diff options
| author | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 |
| commit | 85fbf49ce0e2274d0acf798f6e703747674feec3 (patch) | |
| tree | 158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_lint/src | |
| parent | db534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff) | |
| parent | 9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (diff) | |
| download | rust-85fbf49ce0e2274d0acf798f6e703747674feec3.tar.gz rust-85fbf49ce0e2274d0acf798f6e703747674feec3.zip | |
Auto merge of #74862 - mark-i-m:mv-compiler, r=petrochenkov
Move almost all compiler crates to compiler/ This PR implements https://github.com/rust-lang/compiler-team/issues/336 and moves all `rustc_*` crates from `src` to the new `compiler` directory. `librustc_foo` directories are renamed to `rustc_foo`. `src` directories are introduced inside `rustc_*` directories to mirror the scheme already use for `library` crates.
Diffstat (limited to 'compiler/rustc_lint/src')
| -rw-r--r-- | compiler/rustc_lint/src/array_into_iter.rs | 97 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/builtin.rs | 2422 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/context.rs | 862 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/early.rs | 381 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/internal.rs | 247 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/late.rs | 499 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/levels.rs | 576 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 464 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/non_ascii_idents.rs | 240 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/nonstandard_style.rs | 456 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/nonstandard_style/tests.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/passes.rs | 285 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/redundant_semicolon.rs | 41 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/types.rs | 1240 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/unused.rs | 1011 |
15 files changed, 8842 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs new file mode 100644 index 00000000000..9d74ad3b2f5 --- /dev/null +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -0,0 +1,97 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, Adjustment}; +use rustc_session::lint::FutureIncompatibleInfo; +use rustc_span::symbol::sym; + +declare_lint! { + pub ARRAY_INTO_ITER, + Warn, + "detects calling `into_iter` on arrays", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #66145 <https://github.com/rust-lang/rust/issues/66145>", + edition: None, + }; +} + +declare_lint_pass!( + /// Checks for instances of calling `into_iter` on arrays. + ArrayIntoIter => [ARRAY_INTO_ITER] +); + +impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + // We only care about method call expressions. + if let hir::ExprKind::MethodCall(call, span, args, _) = &expr.kind { + if call.ident.name != sym::into_iter { + return; + } + + // Check if the method call actually calls the libcore + // `IntoIterator::into_iter`. + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + match cx.tcx.trait_of_item(def_id) { + Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {} + _ => return, + }; + + // As this is a method call expression, we have at least one + // argument. + let receiver_arg = &args[0]; + + // Peel all `Box<_>` layers. We have to special case `Box` here as + // `Box` is the only thing that values can be moved out of via + // method call. `Box::new([1]).into_iter()` should trigger this + // lint. + let mut recv_ty = cx.typeck_results().expr_ty(receiver_arg); + let mut num_box_derefs = 0; + while recv_ty.is_box() { + num_box_derefs += 1; + recv_ty = recv_ty.boxed_ty(); + } + + // Make sure we found an array after peeling the boxes. + if !matches!(recv_ty.kind, ty::Array(..)) { + return; + } + + // Make sure that there is an autoref coercion at the expected + // position. The first `num_box_derefs` adjustments are the derefs + // of the box. + match cx.typeck_results().expr_adjustments(receiver_arg).get(num_box_derefs) { + Some(Adjustment { kind: Adjust::Borrow(_), .. }) => {} + _ => return, + } + + // Emit lint diagnostic. + let target = match cx.typeck_results().expr_ty_adjusted(receiver_arg).kind { + ty::Ref(_, ty::TyS { kind: ty::Array(..), .. }, _) => "[T; N]", + ty::Ref(_, ty::TyS { kind: ty::Slice(..), .. }, _) => "[T]", + + // We know the original first argument type is an array type, + // we know that the first adjustment was an autoref coercion + // and we know that `IntoIterator` is the trait involved. The + // array cannot be coerced to something other than a reference + // to an array or to a slice. + _ => bug!("array type coerced to something other than array or slice"), + }; + cx.struct_span_lint(ARRAY_INTO_ITER, *span, |lint| { + lint.build(&format!( + "this method call currently resolves to `<&{} as IntoIterator>::into_iter` (due \ + to autoref coercions), but that might change in the future when \ + `IntoIterator` impls for arrays are added.", + target, + )) + .span_suggestion( + call.ident.span, + "use `.iter()` instead of `.into_iter()` to avoid ambiguity", + "iter".into(), + Applicability::MachineApplicable, + ) + .emit(); + }) + } + } +} diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs new file mode 100644 index 00000000000..ea624b9ed30 --- /dev/null +++ b/compiler/rustc_lint/src/builtin.rs @@ -0,0 +1,2422 @@ +//! Lints in the Rust compiler. +//! +//! This contains lints which can feasibly be implemented as their own +//! AST visitor. Also see `rustc_session::lint::builtin`, which contains the +//! definitions of lints that are emitted directly inside the main compiler. +//! +//! To add a new lint to rustc, declare it here using `declare_lint!()`. +//! Then add code to emit the new lint in the appropriate circumstances. +//! You can do that in an existing `LintPass` if it makes sense, or in a +//! new `LintPass`, or using `Session::add_lint` elsewhere in the +//! compiler. Only do the latter if the check can't be written cleanly as a +//! `LintPass` (also, note that such lints will need to be defined in +//! `rustc_session::lint::builtin`, not here). +//! +//! If you define a new `EarlyLintPass`, you will also need to add it to the +//! `add_early_builtin!` or `add_early_builtin_with_new!` invocation in +//! `lib.rs`. Use the former for unit-like structs and the latter for structs +//! with a `pub fn new()`. +//! +//! If you define a new `LateLintPass`, you will also need to add it to the +//! `late_lint_methods!` invocation in `lib.rs`. + +use crate::{ + types::CItemKind, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, +}; +use rustc_ast::attr::{self, HasAttrs}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::visit::{FnCtxt, FnKind}; +use rustc_ast::{self as ast, *}; +use rustc_ast_pretty::pprust::{self, expr_to_string}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString}; +use rustc_feature::{deprecated_attributes, AttributeGate, AttributeTemplate, AttributeType}; +use rustc_feature::{GateIssue, Stability}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{ForeignItemKind, GenericParamKind, PatKind}; +use rustc_hir::{HirId, HirIdSet, Node}; +use rustc_index::vec::Idx; +use rustc_middle::lint::LintDiagnosticBuilder; +use rustc_middle::ty::subst::{GenericArgKind, Subst}; +use rustc_middle::ty::{self, layout::LayoutError, Ty, TyCtxt}; +use rustc_session::lint::FutureIncompatibleInfo; +use rustc_session::Session; +use rustc_span::edition::Edition; +use rustc_span::source_map::Spanned; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::{BytePos, Span}; +use rustc_target::abi::{LayoutOf, VariantIdx}; +use rustc_trait_selection::traits::misc::can_type_implement_copy; + +use crate::nonstandard_style::{method_context, MethodLateContext}; + +use std::fmt::Write; +use tracing::{debug, trace}; + +// hardwired lints from librustc_middle +pub use rustc_session::lint::builtin::*; + +declare_lint! { + WHILE_TRUE, + Warn, + "suggest using `loop { }` instead of `while true { }`" +} + +declare_lint_pass!(WhileTrue => [WHILE_TRUE]); + +/// Traverse through any amount of parenthesis and return the first non-parens expression. +fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr { + while let ast::ExprKind::Paren(sub) = &expr.kind { + expr = sub; + } + expr +} + +impl EarlyLintPass for WhileTrue { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::While(cond, ..) = &e.kind { + if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind { + if let ast::LitKind::Bool(true) = lit.kind { + if !lit.span.from_expansion() { + let msg = "denote infinite loops with `loop { ... }`"; + let condition_span = cx.sess.source_map().guess_head_span(e.span); + cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| { + lint.build(msg) + .span_suggestion_short( + condition_span, + "use `loop`", + "loop".to_owned(), + Applicability::MachineApplicable, + ) + .emit(); + }) + } + } + } + } + } +} + +declare_lint! { + BOX_POINTERS, + Allow, + "use of owned (Box type) heap memory" +} + +declare_lint_pass!(BoxPointers => [BOX_POINTERS]); + +impl BoxPointers { + fn check_heap_type(&self, cx: &LateContext<'_>, span: Span, ty: Ty<'_>) { + for leaf in ty.walk() { + if let GenericArgKind::Type(leaf_ty) = leaf.unpack() { + if leaf_ty.is_box() { + cx.struct_span_lint(BOX_POINTERS, span, |lint| { + lint.build(&format!("type uses owned (Box type) pointers: {}", ty)).emit() + }); + } + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for BoxPointers { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Fn(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Union(..) => { + let def_id = cx.tcx.hir().local_def_id(it.hir_id); + self.check_heap_type(cx, it.span, cx.tcx.type_of(def_id)) + } + _ => (), + } + + // If it's a struct, we also have to check the fields' types + match it.kind { + hir::ItemKind::Struct(ref struct_def, _) | hir::ItemKind::Union(ref struct_def, _) => { + for struct_field in struct_def.fields() { + let def_id = cx.tcx.hir().local_def_id(struct_field.hir_id); + self.check_heap_type(cx, struct_field.span, cx.tcx.type_of(def_id)); + } + } + _ => (), + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { + let ty = cx.typeck_results().node_type(e.hir_id); + self.check_heap_type(cx, e.span, ty); + } +} + +declare_lint! { + NON_SHORTHAND_FIELD_PATTERNS, + Warn, + "using `Struct { x: x }` instead of `Struct { x }` in a pattern" +} + +declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]); + +impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns { + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { + if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind { + let variant = cx + .typeck_results() + .pat_ty(pat) + .ty_adt_def() + .expect("struct pattern type is not an ADT") + .variant_of_res(cx.qpath_res(qpath, pat.hir_id)); + for fieldpat in field_pats { + if fieldpat.is_shorthand { + continue; + } + if fieldpat.span.from_expansion() { + // Don't lint if this is a macro expansion: macro authors + // shouldn't have to worry about this kind of style issue + // (Issue #49588) + continue; + } + if let PatKind::Binding(binding_annot, _, ident, None) = fieldpat.pat.kind { + if cx.tcx.find_field_index(ident, &variant) + == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results())) + { + cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| { + let mut err = lint + .build(&format!("the `{}:` in this pattern is redundant", ident)); + let binding = match binding_annot { + hir::BindingAnnotation::Unannotated => None, + hir::BindingAnnotation::Mutable => Some("mut"), + hir::BindingAnnotation::Ref => Some("ref"), + hir::BindingAnnotation::RefMut => Some("ref mut"), + }; + let ident = if let Some(binding) = binding { + format!("{} {}", binding, ident) + } else { + ident.to_string() + }; + err.span_suggestion( + fieldpat.span, + "use shorthand field pattern", + ident, + Applicability::MachineApplicable, + ); + err.emit(); + }); + } + } + } + } + } +} + +declare_lint! { + UNSAFE_CODE, + Allow, + "usage of `unsafe` code" +} + +declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]); + +impl UnsafeCode { + fn report_unsafe( + &self, + cx: &EarlyContext<'_>, + span: Span, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ) { + // This comes from a macro that has `#[allow_internal_unsafe]`. + if span.allows_unsafe() { + return; + } + + cx.struct_span_lint(UNSAFE_CODE, span, decorate); + } +} + +impl EarlyLintPass for UnsafeCode { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if cx.sess().check_name(attr, sym::allow_internal_unsafe) { + self.report_unsafe(cx, attr.span, |lint| { + lint.build( + "`allow_internal_unsafe` allows defining \ + macros using unsafe without triggering \ + the `unsafe_code` lint at their call site", + ) + .emit() + }); + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::Block(ref blk, _) = e.kind { + // Don't warn about generated blocks; that'll just pollute the output. + if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) { + self.report_unsafe(cx, blk.span, |lint| { + lint.build("usage of an `unsafe` block").emit() + }); + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { + match it.kind { + ast::ItemKind::Trait(_, ast::Unsafe::Yes(_), ..) => { + self.report_unsafe(cx, it.span, |lint| { + lint.build("declaration of an `unsafe` trait").emit() + }) + } + + ast::ItemKind::Impl { unsafety: ast::Unsafe::Yes(_), .. } => { + self.report_unsafe(cx, it.span, |lint| { + lint.build("implementation of an `unsafe` trait").emit() + }) + } + + _ => {} + } + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) { + if let FnKind::Fn( + ctxt, + _, + ast::FnSig { header: ast::FnHeader { unsafety: ast::Unsafe::Yes(_), .. }, .. }, + _, + body, + ) = fk + { + let msg = match ctxt { + FnCtxt::Foreign => return, + FnCtxt::Free => "declaration of an `unsafe` function", + FnCtxt::Assoc(_) if body.is_none() => "declaration of an `unsafe` method", + FnCtxt::Assoc(_) => "implementation of an `unsafe` method", + }; + self.report_unsafe(cx, span, |lint| lint.build(msg).emit()); + } + } +} + +declare_lint! { + pub MISSING_DOCS, + Allow, + "detects missing documentation for public members", + report_in_external_macro +} + +pub struct MissingDoc { + /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes. + doc_hidden_stack: Vec<bool>, + + /// Private traits or trait items that leaked through. Don't check their methods. + private_traits: FxHashSet<hir::HirId>, +} + +impl_lint_pass!(MissingDoc => [MISSING_DOCS]); + +fn has_doc(sess: &Session, attr: &ast::Attribute) -> bool { + if attr.is_doc_comment() { + return true; + } + + if !sess.check_name(attr, sym::doc) { + return false; + } + + if attr.is_value_str() { + return true; + } + + if let Some(list) = attr.meta_item_list() { + for meta in list { + if meta.has_name(sym::include) || meta.has_name(sym::hidden) { + return true; + } + } + } + + false +} + +impl MissingDoc { + pub fn new() -> MissingDoc { + MissingDoc { doc_hidden_stack: vec![false], private_traits: FxHashSet::default() } + } + + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") + } + + fn check_missing_docs_attrs( + &self, + cx: &LateContext<'_>, + id: Option<hir::HirId>, + attrs: &[ast::Attribute], + sp: Span, + article: &'static str, + desc: &'static str, + ) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.sess().opts.test { + return; + } + + // `#[doc(hidden)]` disables missing_docs check. + if self.doc_hidden() { + return; + } + + // Only check publicly-visible items, using the result from the privacy pass. + // It's an option so the crate root can also use this function (it doesn't + // have a `NodeId`). + if let Some(id) = id { + if !cx.access_levels.is_exported(id) { + return; + } + } + + let has_doc = attrs.iter().any(|a| has_doc(cx.sess(), a)); + if !has_doc { + cx.struct_span_lint( + MISSING_DOCS, + cx.tcx.sess.source_map().guess_head_span(sp), + |lint| { + lint.build(&format!("missing documentation for {} {}", article, desc)).emit() + }, + ); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for MissingDoc { + fn enter_lint_attrs(&mut self, cx: &LateContext<'_>, attrs: &[ast::Attribute]) { + let doc_hidden = self.doc_hidden() + || attrs.iter().any(|attr| { + cx.sess().check_name(attr, sym::doc) + && match attr.meta_item_list() { + None => false, + Some(l) => attr::list_contains_name(&l, sym::hidden), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } + + fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } + + fn check_crate(&mut self, cx: &LateContext<'_>, krate: &hir::Crate<'_>) { + self.check_missing_docs_attrs(cx, None, &krate.item.attrs, krate.item.span, "the", "crate"); + + for macro_def in krate.exported_macros { + let has_doc = macro_def.attrs.iter().any(|a| has_doc(cx.sess(), a)); + if !has_doc { + cx.struct_span_lint( + MISSING_DOCS, + cx.tcx.sess.source_map().guess_head_span(macro_def.span), + |lint| lint.build("missing documentation for macro").emit(), + ); + } + } + } + + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Trait(.., trait_item_refs) => { + // Issue #11592: traits are always considered exported, even when private. + if let hir::VisibilityKind::Inherited = it.vis.node { + self.private_traits.insert(it.hir_id); + for trait_item_ref in trait_item_refs { + self.private_traits.insert(trait_item_ref.id.hir_id); + } + return; + } + } + hir::ItemKind::Impl { of_trait: Some(ref trait_ref), items, .. } => { + // If the trait is private, add the impl items to `private_traits` so they don't get + // reported for missing docs. + let real_trait = trait_ref.path.res.def_id(); + if let Some(def_id) = real_trait.as_local() { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); + if let Some(Node::Item(item)) = cx.tcx.hir().find(hir_id) { + if let hir::VisibilityKind::Inherited = item.vis.node { + for impl_item_ref in items { + self.private_traits.insert(impl_item_ref.id.hir_id); + } + } + } + } + return; + } + + hir::ItemKind::TyAlias(..) + | hir::ItemKind::Fn(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::Const(..) + | hir::ItemKind::Static(..) => {} + + _ => return, + }; + + let def_id = cx.tcx.hir().local_def_id(it.hir_id); + let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id()); + + self.check_missing_docs_attrs(cx, Some(it.hir_id), &it.attrs, it.span, article, desc); + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) { + if self.private_traits.contains(&trait_item.hir_id) { + return; + } + + let def_id = cx.tcx.hir().local_def_id(trait_item.hir_id); + let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id()); + + self.check_missing_docs_attrs( + cx, + Some(trait_item.hir_id), + &trait_item.attrs, + trait_item.span, + article, + desc, + ); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + // If the method is an impl for a trait, don't doc. + if method_context(cx, impl_item.hir_id) == MethodLateContext::TraitImpl { + return; + } + + let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id); + let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id()); + self.check_missing_docs_attrs( + cx, + Some(impl_item.hir_id), + &impl_item.attrs, + impl_item.span, + article, + desc, + ); + } + + fn check_struct_field(&mut self, cx: &LateContext<'_>, sf: &hir::StructField<'_>) { + if !sf.is_positional() { + self.check_missing_docs_attrs( + cx, + Some(sf.hir_id), + &sf.attrs, + sf.span, + "a", + "struct field", + ) + } + } + + fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) { + self.check_missing_docs_attrs(cx, Some(v.id), &v.attrs, v.span, "a", "variant"); + } +} + +declare_lint! { + pub MISSING_COPY_IMPLEMENTATIONS, + Allow, + "detects potentially-forgotten implementations of `Copy`" +} + +declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]); + +impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + if !cx.access_levels.is_reachable(item.hir_id) { + return; + } + let (def, ty) = match item.kind { + hir::ItemKind::Struct(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id)); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + hir::ItemKind::Union(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id)); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + hir::ItemKind::Enum(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id)); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + _ => return, + }; + if def.has_dtor(cx.tcx) { + return; + } + let param_env = ty::ParamEnv::empty(); + if ty.is_copy_modulo_regions(cx.tcx.at(item.span), param_env) { + return; + } + if can_type_implement_copy(cx.tcx, param_env, ty).is_ok() { + cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| { + lint.build( + "type could implement `Copy`; consider adding `impl \ + Copy`", + ) + .emit() + }) + } + } +} + +declare_lint! { + MISSING_DEBUG_IMPLEMENTATIONS, + Allow, + "detects missing implementations of Debug" +} + +#[derive(Default)] +pub struct MissingDebugImplementations { + impling_types: Option<HirIdSet>, +} + +impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]); + +impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + if !cx.access_levels.is_reachable(item.hir_id) { + return; + } + + match item.kind { + hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} + _ => return, + } + + let debug = match cx.tcx.get_diagnostic_item(sym::debug_trait) { + Some(debug) => debug, + None => return, + }; + + if self.impling_types.is_none() { + let mut impls = HirIdSet::default(); + cx.tcx.for_each_impl(debug, |d| { + if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { + if let Some(def_id) = ty_def.did.as_local() { + impls.insert(cx.tcx.hir().local_def_id_to_hir_id(def_id)); + } + } + }); + + self.impling_types = Some(impls); + debug!("{:?}", self.impling_types); + } + + if !self.impling_types.as_ref().unwrap().contains(&item.hir_id) { + cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| { + lint.build(&format!( + "type does not implement `{}`; consider adding `#[derive(Debug)]` \ + or a manual implementation", + cx.tcx.def_path_str(debug) + )) + .emit() + }); + } + } +} + +declare_lint! { + pub ANONYMOUS_PARAMETERS, + Allow, + "detects anonymous parameters", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>", + edition: Some(Edition::Edition2018), + }; +} + +declare_lint_pass!( + /// Checks for use of anonymous parameters (RFC 1685). + AnonymousParameters => [ANONYMOUS_PARAMETERS] +); + +impl EarlyLintPass for AnonymousParameters { + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::Fn(_, ref sig, _, _) = it.kind { + for arg in sig.decl.inputs.iter() { + if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind { + if ident.name == kw::Invalid { + cx.struct_span_lint(ANONYMOUS_PARAMETERS, arg.pat.span, |lint| { + let ty_snip = cx.sess.source_map().span_to_snippet(arg.ty.span); + + let (ty_snip, appl) = if let Ok(ref snip) = ty_snip { + (snip.as_str(), Applicability::MachineApplicable) + } else { + ("<type>", Applicability::HasPlaceholders) + }; + + lint.build( + "anonymous parameters are deprecated and will be \ + removed in the next edition.", + ) + .span_suggestion( + arg.pat.span, + "try naming the parameter or explicitly \ + ignoring it", + format!("_: {}", ty_snip), + appl, + ) + .emit(); + }) + } + } + } + } + } +} + +/// Check for use of attributes which have been deprecated. +#[derive(Clone)] +pub struct DeprecatedAttr { + // This is not free to compute, so we want to keep it around, rather than + // compute it for every attribute. + depr_attrs: Vec<&'static (Symbol, AttributeType, AttributeTemplate, AttributeGate)>, +} + +impl_lint_pass!(DeprecatedAttr => []); + +impl DeprecatedAttr { + pub fn new() -> DeprecatedAttr { + DeprecatedAttr { depr_attrs: deprecated_attributes() } + } +} + +fn lint_deprecated_attr( + cx: &EarlyContext<'_>, + attr: &ast::Attribute, + msg: &str, + suggestion: Option<&str>, +) { + cx.struct_span_lint(DEPRECATED, attr.span, |lint| { + lint.build(msg) + .span_suggestion_short( + attr.span, + suggestion.unwrap_or("remove this attribute"), + String::new(), + Applicability::MachineApplicable, + ) + .emit(); + }) +} + +impl EarlyLintPass for DeprecatedAttr { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { + for &&(n, _, _, ref g) in &self.depr_attrs { + if attr.ident().map(|ident| ident.name) == Some(n) { + if let &AttributeGate::Gated( + Stability::Deprecated(link, suggestion), + ref name, + ref reason, + _, + ) = g + { + let msg = + format!("use of deprecated attribute `{}`: {}. See {}", name, reason, link); + lint_deprecated_attr(cx, attr, &msg, suggestion); + } + return; + } + } + if cx.sess().check_name(attr, sym::no_start) || cx.sess().check_name(attr, sym::crate_id) { + let path_str = pprust::path_to_string(&attr.get_normal_item().path); + let msg = format!("use of deprecated attribute `{}`: no longer used.", path_str); + lint_deprecated_attr(cx, attr, &msg, None); + } + } +} + +fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) { + let mut attrs = attrs.iter().peekable(); + + // Accumulate a single span for sugared doc comments. + let mut sugared_span: Option<Span> = None; + + while let Some(attr) = attrs.next() { + if attr.is_doc_comment() { + sugared_span = + Some(sugared_span.map_or_else(|| attr.span, |span| span.with_hi(attr.span.hi()))); + } + + if attrs.peek().map(|next_attr| next_attr.is_doc_comment()).unwrap_or_default() { + continue; + } + + let span = sugared_span.take().unwrap_or_else(|| attr.span); + + if attr.is_doc_comment() || cx.sess().check_name(attr, sym::doc) { + cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| { + let mut err = lint.build("unused doc comment"); + err.span_label( + node_span, + format!("rustdoc does not generate documentation for {}", node_kind), + ); + err.emit(); + }); + } + } +} + +impl EarlyLintPass for UnusedDocComment { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { + let kind = match stmt.kind { + ast::StmtKind::Local(..) => "statements", + ast::StmtKind::Item(..) => "inner items", + // expressions will be reported by `check_expr`. + ast::StmtKind::Empty + | ast::StmtKind::Semi(_) + | ast::StmtKind::Expr(_) + | ast::StmtKind::MacCall(_) => return, + }; + + warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs()); + } + + fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { + let arm_span = arm.pat.span.with_hi(arm.body.span.hi()); + warn_if_doc(cx, arm_span, "match arms", &arm.attrs); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + warn_if_doc(cx, expr.span, "expressions", &expr.attrs); + } +} + +declare_lint! { + NO_MANGLE_CONST_ITEMS, + Deny, + "const items will not have their symbols exported" +} + +declare_lint! { + NO_MANGLE_GENERIC_ITEMS, + Warn, + "generic items must be mangled" +} + +declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS]); + +impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Fn(.., ref generics, _) => { + if let Some(no_mangle_attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) { + for param in generics.params { + match param.kind { + GenericParamKind::Lifetime { .. } => {} + GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => { + cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, it.span, |lint| { + lint.build( + "functions generic over types or consts must be mangled", + ) + .span_suggestion_short( + no_mangle_attr.span, + "remove this attribute", + String::new(), + // Use of `#[no_mangle]` suggests FFI intent; correct + // fix may be to monomorphize source by hand + Applicability::MaybeIncorrect, + ) + .emit(); + }); + break; + } + } + } + } + } + hir::ItemKind::Const(..) => { + if cx.sess().contains_name(&it.attrs, sym::no_mangle) { + // Const items do not refer to a particular location in memory, and therefore + // don't have anything to attach a symbol to + cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| { + let msg = "const items should never be `#[no_mangle]`"; + let mut err = lint.build(msg); + + // account for "pub const" (#45562) + let start = cx + .tcx + .sess + .source_map() + .span_to_snippet(it.span) + .map(|snippet| snippet.find("const").unwrap_or(0)) + .unwrap_or(0) as u32; + // `const` is 5 chars + let const_span = it.span.with_hi(BytePos(it.span.lo().0 + start + 5)); + err.span_suggestion( + const_span, + "try a static value", + "pub static".to_owned(), + Applicability::MachineApplicable, + ); + err.emit(); + }); + } + } + _ => {} + } + } +} + +declare_lint! { + MUTABLE_TRANSMUTES, + Deny, + "mutating transmuted &mut T from &T may cause undefined behavior" +} + +declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]); + +impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + use rustc_target::spec::abi::Abi::RustIntrinsic; + if let Some((&ty::Ref(_, _, from_mt), &ty::Ref(_, _, to_mt))) = + get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (&ty1.kind, &ty2.kind)) + { + if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not { + let msg = "mutating transmuted &mut T from &T may cause undefined behavior, \ + consider instead using an UnsafeCell"; + cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| lint.build(msg).emit()); + } + } + + fn get_transmute_from_to<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + ) -> Option<(Ty<'tcx>, Ty<'tcx>)> { + let def = if let hir::ExprKind::Path(ref qpath) = expr.kind { + cx.qpath_res(qpath, expr.hir_id) + } else { + return None; + }; + if let Res::Def(DefKind::Fn, did) = def { + if !def_id_is_transmute(cx, did) { + return None; + } + let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx); + let from = sig.inputs().skip_binder()[0]; + let to = sig.output().skip_binder(); + return Some((from, to)); + } + None + } + + fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { + cx.tcx.fn_sig(def_id).abi() == RustIntrinsic + && cx.tcx.item_name(def_id) == sym::transmute + } + } +} + +declare_lint! { + UNSTABLE_FEATURES, + Allow, + "enabling unstable features (deprecated. do not use)" +} + +declare_lint_pass!( + /// Forbids using the `#[feature(...)]` attribute + UnstableFeatures => [UNSTABLE_FEATURES] +); + +impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { + if cx.sess().check_name(attr, sym::feature) { + if let Some(items) = attr.meta_item_list() { + for item in items { + cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| { + lint.build("unstable feature").emit() + }); + } + } + } + } +} + +declare_lint! { + pub UNREACHABLE_PUB, + Allow, + "`pub` items not reachable from crate root" +} + +declare_lint_pass!( + /// Lint for items marked `pub` that aren't reachable from other crates. + UnreachablePub => [UNREACHABLE_PUB] +); + +impl UnreachablePub { + fn perform_lint( + &self, + cx: &LateContext<'_>, + what: &str, + id: hir::HirId, + vis: &hir::Visibility<'_>, + span: Span, + exportable: bool, + ) { + let mut applicability = Applicability::MachineApplicable; + match vis.node { + hir::VisibilityKind::Public if !cx.access_levels.is_reachable(id) => { + if span.from_expansion() { + applicability = Applicability::MaybeIncorrect; + } + let def_span = cx.tcx.sess.source_map().guess_head_span(span); + cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| { + let mut err = lint.build(&format!("unreachable `pub` {}", what)); + let replacement = if cx.tcx.features().crate_visibility_modifier { + "crate" + } else { + "pub(crate)" + } + .to_owned(); + + err.span_suggestion( + vis.span, + "consider restricting its visibility", + replacement, + applicability, + ); + if exportable { + err.help("or consider exporting it for use by other crates"); + } + err.emit(); + }); + } + _ => {} + } + } +} + +impl<'tcx> LateLintPass<'tcx> for UnreachablePub { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + self.perform_lint(cx, "item", item.hir_id, &item.vis, item.span, true); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'tcx>) { + self.perform_lint( + cx, + "item", + foreign_item.hir_id, + &foreign_item.vis, + foreign_item.span, + true, + ); + } + + fn check_struct_field(&mut self, cx: &LateContext<'_>, field: &hir::StructField<'_>) { + self.perform_lint(cx, "field", field.hir_id, &field.vis, field.span, false); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + self.perform_lint(cx, "item", impl_item.hir_id, &impl_item.vis, impl_item.span, false); + } +} + +declare_lint! { + TYPE_ALIAS_BOUNDS, + Warn, + "bounds in type aliases are not enforced" +} + +declare_lint_pass!( + /// Lint for trait and lifetime bounds in type aliases being mostly ignored. + /// They are relevant when using associated types, but otherwise neither checked + /// at definition site nor enforced at use site. + TypeAliasBounds => [TYPE_ALIAS_BOUNDS] +); + +impl TypeAliasBounds { + fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool { + match *qpath { + hir::QPath::TypeRelative(ref ty, _) => { + // If this is a type variable, we found a `T::Assoc`. + match ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => match path.res { + Res::Def(DefKind::TyParam, _) => true, + _ => false, + }, + _ => false, + } + } + hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false, + } + } + + fn suggest_changing_assoc_types(ty: &hir::Ty<'_>, err: &mut DiagnosticBuilder<'_>) { + // Access to associates types should use `<T as Bound>::Assoc`, which does not need a + // bound. Let's see if this type does that. + + // We use a HIR visitor to walk the type. + use rustc_hir::intravisit::{self, Visitor}; + struct WalkAssocTypes<'a, 'db> { + err: &'a mut DiagnosticBuilder<'db>, + } + impl<'a, 'db, 'v> Visitor<'v> for WalkAssocTypes<'a, 'db> { + type Map = intravisit::ErasedMap<'v>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::None + } + + fn visit_qpath(&mut self, qpath: &'v hir::QPath<'v>, id: hir::HirId, span: Span) { + if TypeAliasBounds::is_type_variable_assoc(qpath) { + self.err.span_help( + span, + "use fully disambiguated paths (i.e., `<T as Trait>::Assoc`) to refer to \ + associated types in type aliases", + ); + } + intravisit::walk_qpath(self, qpath, id, span) + } + } + + // Let's go for a walk! + let mut visitor = WalkAssocTypes { err }; + visitor.visit_ty(ty); + } +} + +impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + let (ty, type_alias_generics) = match item.kind { + hir::ItemKind::TyAlias(ref ty, ref generics) => (&*ty, generics), + _ => return, + }; + if let hir::TyKind::OpaqueDef(..) = ty.kind { + // Bounds are respected for `type X = impl Trait` + return; + } + let mut suggested_changing_assoc_types = false; + // There must not be a where clause + if !type_alias_generics.where_clause.predicates.is_empty() { + cx.lint( + TYPE_ALIAS_BOUNDS, + |lint| { + let mut err = lint.build("where clauses are not enforced in type aliases"); + let spans: Vec<_> = type_alias_generics + .where_clause + .predicates + .iter() + .map(|pred| pred.span()) + .collect(); + err.set_span(spans); + err.span_suggestion( + type_alias_generics.where_clause.span_for_predicates_or_empty_place(), + "the clause will not be checked when the type alias is used, and should be removed", + String::new(), + Applicability::MachineApplicable, + ); + if !suggested_changing_assoc_types { + TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err); + suggested_changing_assoc_types = true; + } + err.emit(); + }, + ); + } + // The parameters must not have bounds + for param in type_alias_generics.params.iter() { + let spans: Vec<_> = param.bounds.iter().map(|b| b.span()).collect(); + let suggestion = spans + .iter() + .map(|sp| { + let start = param.span.between(*sp); // Include the `:` in `T: Bound`. + (start.to(*sp), String::new()) + }) + .collect(); + if !spans.is_empty() { + cx.struct_span_lint(TYPE_ALIAS_BOUNDS, spans, |lint| { + let mut err = + lint.build("bounds on generic parameters are not enforced in type aliases"); + let msg = "the bound will not be checked when the type alias is used, \ + and should be removed"; + err.multipart_suggestion(&msg, suggestion, Applicability::MachineApplicable); + if !suggested_changing_assoc_types { + TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err); + suggested_changing_assoc_types = true; + } + err.emit(); + }); + } + } + } +} + +declare_lint_pass!( + /// Lint constants that are erroneous. + /// Without this lint, we might not get any diagnostic if the constant is + /// unused within this crate, even though downstream crates can't use it + /// without producing an error. + UnusedBrokenConst => [] +); + +fn check_const(cx: &LateContext<'_>, body_id: hir::BodyId) { + let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); + // trigger the query once for all constants since that will already report the errors + // FIXME: Use ensure here + let _ = cx.tcx.const_eval_poly(def_id); +} + +impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Const(_, body_id) => { + check_const(cx, body_id); + } + hir::ItemKind::Static(_, _, body_id) => { + check_const(cx, body_id); + } + _ => {} + } + } +} + +declare_lint! { + TRIVIAL_BOUNDS, + Warn, + "these bounds don't depend on an type parameters" +} + +declare_lint_pass!( + /// Lint for trait and lifetime bounds that don't depend on type parameters + /// which either do nothing, or stop the item from being used. + TrivialConstraints => [TRIVIAL_BOUNDS] +); + +impl<'tcx> LateLintPass<'tcx> for TrivialConstraints { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + use rustc_middle::ty::fold::TypeFoldable; + use rustc_middle::ty::PredicateAtom::*; + + if cx.tcx.features().trivial_bounds { + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + let predicates = cx.tcx.predicates_of(def_id); + for &(predicate, span) in predicates.predicates { + let predicate_kind_name = match predicate.skip_binders() { + Trait(..) => "Trait", + TypeOutlives(..) | + RegionOutlives(..) => "Lifetime", + + // Ignore projections, as they can only be global + // if the trait bound is global + Projection(..) | + // Ignore bounds that a user can't type + WellFormed(..) | + ObjectSafe(..) | + ClosureKind(..) | + Subtype(..) | + ConstEvaluatable(..) | + ConstEquate(..) => continue, + }; + if predicate.is_global() { + cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| { + lint.build(&format!( + "{} bound {} does not depend on any type \ + or lifetime parameters", + predicate_kind_name, predicate + )) + .emit() + }); + } + } + } + } +} + +declare_lint_pass!( + /// Does nothing as a lint pass, but registers some `Lint`s + /// which are used by other parts of the compiler. + SoftLints => [ + WHILE_TRUE, + BOX_POINTERS, + NON_SHORTHAND_FIELD_PATTERNS, + UNSAFE_CODE, + MISSING_DOCS, + MISSING_COPY_IMPLEMENTATIONS, + MISSING_DEBUG_IMPLEMENTATIONS, + ANONYMOUS_PARAMETERS, + UNUSED_DOC_COMMENTS, + NO_MANGLE_CONST_ITEMS, + NO_MANGLE_GENERIC_ITEMS, + MUTABLE_TRANSMUTES, + UNSTABLE_FEATURES, + UNREACHABLE_PUB, + TYPE_ALIAS_BOUNDS, + TRIVIAL_BOUNDS + ] +); + +declare_lint! { + pub ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + Warn, + "`...` range patterns are deprecated" +} + +#[derive(Default)] +pub struct EllipsisInclusiveRangePatterns { + /// If `Some(_)`, suppress all subsequent pattern + /// warnings for better diagnostics. + node_id: Option<ast::NodeId>, +} + +impl_lint_pass!(EllipsisInclusiveRangePatterns => [ELLIPSIS_INCLUSIVE_RANGE_PATTERNS]); + +impl EarlyLintPass for EllipsisInclusiveRangePatterns { + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) { + if self.node_id.is_some() { + // Don't recursively warn about patterns inside range endpoints. + return; + } + + use self::ast::{PatKind, RangeSyntax::DotDotDot}; + + /// If `pat` is a `...` pattern, return the start and end of the range, as well as the span + /// corresponding to the ellipsis. + fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> { + match &pat.kind { + PatKind::Range( + a, + Some(b), + Spanned { span, node: RangeEnd::Included(DotDotDot) }, + ) => Some((a.as_deref(), b, *span)), + _ => None, + } + } + + let (parenthesise, endpoints) = match &pat.kind { + PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(&subpat)), + _ => (false, matches_ellipsis_pat(pat)), + }; + + if let Some((start, end, join)) = endpoints { + let msg = "`...` range patterns are deprecated"; + let suggestion = "use `..=` for an inclusive range"; + if parenthesise { + self.node_id = Some(pat.id); + cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, |lint| { + let end = expr_to_string(&end); + let replace = match start { + Some(start) => format!("&({}..={})", expr_to_string(&start), end), + None => format!("&(..={})", end), + }; + lint.build(msg) + .span_suggestion( + pat.span, + suggestion, + replace, + Applicability::MachineApplicable, + ) + .emit(); + }); + } else { + cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| { + lint.build(msg) + .span_suggestion_short( + join, + suggestion, + "..=".to_owned(), + Applicability::MachineApplicable, + ) + .emit(); + }); + }; + } + } + + fn check_pat_post(&mut self, _cx: &EarlyContext<'_>, pat: &ast::Pat) { + if let Some(node_id) = self.node_id { + if pat.id == node_id { + self.node_id = None + } + } + } +} + +declare_lint! { + UNNAMEABLE_TEST_ITEMS, + Warn, + "detects an item that cannot be named being marked as `#[test_case]`", + report_in_external_macro +} + +pub struct UnnameableTestItems { + boundary: Option<hir::HirId>, // HirId of the item under which things are not nameable + items_nameable: bool, +} + +impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]); + +impl UnnameableTestItems { + pub fn new() -> Self { + Self { boundary: None, items_nameable: true } + } +} + +impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if self.items_nameable { + if let hir::ItemKind::Mod(..) = it.kind { + } else { + self.items_nameable = false; + self.boundary = Some(it.hir_id); + } + return; + } + + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::rustc_test_marker) { + cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| { + lint.build("cannot test inner items").emit() + }); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) { + if !self.items_nameable && self.boundary == Some(it.hir_id) { + self.items_nameable = true; + } + } +} + +declare_lint! { + pub KEYWORD_IDENTS, + Allow, + "detects edition keywords being used as an identifier", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>", + edition: Some(Edition::Edition2018), + }; +} + +declare_lint_pass!( + /// Check for uses of edition keywords used as an identifier. + KeywordIdents => [KEYWORD_IDENTS] +); + +struct UnderMacro(bool); + +impl KeywordIdents { + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: TokenStream) { + for tt in tokens.into_trees() { + match tt { + // Only report non-raw idents. + TokenTree::Token(token) => { + if let Some((ident, false)) = token.ident() { + self.check_ident_token(cx, UnderMacro(true), ident); + } + } + TokenTree::Delimited(_, _, tts) => self.check_tokens(cx, tts), + } + } + } + + fn check_ident_token( + &mut self, + cx: &EarlyContext<'_>, + UnderMacro(under_macro): UnderMacro, + ident: Ident, + ) { + let next_edition = match cx.sess.edition() { + Edition::Edition2015 => { + match ident.name { + kw::Async | kw::Await | kw::Try => Edition::Edition2018, + + // rust-lang/rust#56327: Conservatively do not + // attempt to report occurrences of `dyn` within + // macro definitions or invocations, because `dyn` + // can legitimately occur as a contextual keyword + // in 2015 code denoting its 2018 meaning, and we + // do not want rustfix to inject bugs into working + // code by rewriting such occurrences. + // + // But if we see `dyn` outside of a macro, we know + // its precise role in the parsed AST and thus are + // assured this is truly an attempt to use it as + // an identifier. + kw::Dyn if !under_macro => Edition::Edition2018, + + _ => return, + } + } + + // There are no new keywords yet for the 2018 edition and beyond. + _ => return, + }; + + // Don't lint `r#foo`. + if cx.sess.parse_sess.raw_identifier_spans.borrow().contains(&ident.span) { + return; + } + + cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| { + lint.build(&format!("`{}` is a keyword in the {} edition", ident, next_edition)) + .span_suggestion( + ident.span, + "you can use a raw identifier to stay compatible", + format!("r#{}", ident), + Applicability::MachineApplicable, + ) + .emit() + }); + } +} + +impl EarlyLintPass for KeywordIdents { + fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef, _id: ast::NodeId) { + self.check_tokens(cx, mac_def.body.inner_tokens()); + } + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + self.check_tokens(cx, mac.args.inner_tokens()); + } + fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) { + self.check_ident_token(cx, UnderMacro(false), ident); + } +} + +declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]); + +impl ExplicitOutlivesRequirements { + fn lifetimes_outliving_lifetime<'tcx>( + inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)], + index: u32, + ) -> Vec<ty::Region<'tcx>> { + inferred_outlives + .iter() + .filter_map(|(pred, _)| match pred.skip_binders() { + ty::PredicateAtom::RegionOutlives(ty::OutlivesPredicate(a, b)) => match a { + ty::ReEarlyBound(ebr) if ebr.index == index => Some(b), + _ => None, + }, + _ => None, + }) + .collect() + } + + fn lifetimes_outliving_type<'tcx>( + inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)], + index: u32, + ) -> Vec<ty::Region<'tcx>> { + inferred_outlives + .iter() + .filter_map(|(pred, _)| match pred.skip_binders() { + ty::PredicateAtom::TypeOutlives(ty::OutlivesPredicate(a, b)) => { + a.is_param(index).then_some(b) + } + _ => None, + }) + .collect() + } + + fn collect_outlived_lifetimes<'tcx>( + &self, + param: &'tcx hir::GenericParam<'tcx>, + tcx: TyCtxt<'tcx>, + inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)], + ty_generics: &'tcx ty::Generics, + ) -> Vec<ty::Region<'tcx>> { + let index = + ty_generics.param_def_id_to_index[&tcx.hir().local_def_id(param.hir_id).to_def_id()]; + + match param.kind { + hir::GenericParamKind::Lifetime { .. } => { + Self::lifetimes_outliving_lifetime(inferred_outlives, index) + } + hir::GenericParamKind::Type { .. } => { + Self::lifetimes_outliving_type(inferred_outlives, index) + } + hir::GenericParamKind::Const { .. } => Vec::new(), + } + } + + fn collect_outlives_bound_spans<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + bounds: &hir::GenericBounds<'_>, + inferred_outlives: &[ty::Region<'tcx>], + infer_static: bool, + ) -> Vec<(usize, Span)> { + use rustc_middle::middle::resolve_lifetime::Region; + + bounds + .iter() + .enumerate() + .filter_map(|(i, bound)| { + if let hir::GenericBound::Outlives(lifetime) = bound { + let is_inferred = match tcx.named_region(lifetime.hir_id) { + Some(Region::Static) if infer_static => inferred_outlives + .iter() + .any(|r| if let ty::ReStatic = r { true } else { false }), + Some(Region::EarlyBound(index, ..)) => inferred_outlives.iter().any(|r| { + if let ty::ReEarlyBound(ebr) = r { ebr.index == index } else { false } + }), + _ => false, + }; + is_inferred.then_some((i, bound.span())) + } else { + None + } + }) + .collect() + } + + fn consolidate_outlives_bound_spans( + &self, + lo: Span, + bounds: &hir::GenericBounds<'_>, + bound_spans: Vec<(usize, Span)>, + ) -> Vec<Span> { + if bounds.is_empty() { + return Vec::new(); + } + if bound_spans.len() == bounds.len() { + let (_, last_bound_span) = bound_spans[bound_spans.len() - 1]; + // If all bounds are inferable, we want to delete the colon, so + // start from just after the parameter (span passed as argument) + vec![lo.to(last_bound_span)] + } else { + let mut merged = Vec::new(); + let mut last_merged_i = None; + + let mut from_start = true; + for (i, bound_span) in bound_spans { + match last_merged_i { + // If the first bound is inferable, our span should also eat the leading `+`. + None if i == 0 => { + merged.push(bound_span.to(bounds[1].span().shrink_to_lo())); + last_merged_i = Some(0); + } + // If consecutive bounds are inferable, merge their spans + Some(h) if i == h + 1 => { + if let Some(tail) = merged.last_mut() { + // Also eat the trailing `+` if the first + // more-than-one bound is inferable + let to_span = if from_start && i < bounds.len() { + bounds[i + 1].span().shrink_to_lo() + } else { + bound_span + }; + *tail = tail.to(to_span); + last_merged_i = Some(i); + } else { + bug!("another bound-span visited earlier"); + } + } + _ => { + // When we find a non-inferable bound, subsequent inferable bounds + // won't be consecutive from the start (and we'll eat the leading + // `+` rather than the trailing one) + from_start = false; + merged.push(bounds[i - 1].span().shrink_to_hi().to(bound_span)); + last_merged_i = Some(i); + } + } + } + merged + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + use rustc_middle::middle::resolve_lifetime::Region; + + let infer_static = cx.tcx.features().infer_static_outlives_requirements; + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + if let hir::ItemKind::Struct(_, ref hir_generics) + | hir::ItemKind::Enum(_, ref hir_generics) + | hir::ItemKind::Union(_, ref hir_generics) = item.kind + { + let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); + if inferred_outlives.is_empty() { + return; + } + + let ty_generics = cx.tcx.generics_of(def_id); + + let mut bound_count = 0; + let mut lint_spans = Vec::new(); + + for param in hir_generics.params { + let has_lifetime_bounds = param.bounds.iter().any(|bound| { + if let hir::GenericBound::Outlives(_) = bound { true } else { false } + }); + if !has_lifetime_bounds { + continue; + } + + let relevant_lifetimes = + self.collect_outlived_lifetimes(param, cx.tcx, inferred_outlives, ty_generics); + if relevant_lifetimes.is_empty() { + continue; + } + + let bound_spans = self.collect_outlives_bound_spans( + cx.tcx, + ¶m.bounds, + &relevant_lifetimes, + infer_static, + ); + bound_count += bound_spans.len(); + lint_spans.extend(self.consolidate_outlives_bound_spans( + param.span.shrink_to_hi(), + ¶m.bounds, + bound_spans, + )); + } + + let mut where_lint_spans = Vec::new(); + let mut dropped_predicate_count = 0; + let num_predicates = hir_generics.where_clause.predicates.len(); + for (i, where_predicate) in hir_generics.where_clause.predicates.iter().enumerate() { + let (relevant_lifetimes, bounds, span) = match where_predicate { + hir::WherePredicate::RegionPredicate(predicate) => { + if let Some(Region::EarlyBound(index, ..)) = + cx.tcx.named_region(predicate.lifetime.hir_id) + { + ( + Self::lifetimes_outliving_lifetime(inferred_outlives, index), + &predicate.bounds, + predicate.span, + ) + } else { + continue; + } + } + hir::WherePredicate::BoundPredicate(predicate) => { + // FIXME we can also infer bounds on associated types, + // and should check for them here. + match predicate.bounded_ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => { + if let Res::Def(DefKind::TyParam, def_id) = path.res { + let index = ty_generics.param_def_id_to_index[&def_id]; + ( + Self::lifetimes_outliving_type(inferred_outlives, index), + &predicate.bounds, + predicate.span, + ) + } else { + continue; + } + } + _ => { + continue; + } + } + } + _ => continue, + }; + if relevant_lifetimes.is_empty() { + continue; + } + + let bound_spans = self.collect_outlives_bound_spans( + cx.tcx, + bounds, + &relevant_lifetimes, + infer_static, + ); + bound_count += bound_spans.len(); + + let drop_predicate = bound_spans.len() == bounds.len(); + if drop_predicate { + dropped_predicate_count += 1; + } + + // If all the bounds on a predicate were inferable and there are + // further predicates, we want to eat the trailing comma. + if drop_predicate && i + 1 < num_predicates { + let next_predicate_span = hir_generics.where_clause.predicates[i + 1].span(); + where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo())); + } else { + where_lint_spans.extend(self.consolidate_outlives_bound_spans( + span.shrink_to_lo(), + bounds, + bound_spans, + )); + } + } + + // If all predicates are inferable, drop the entire clause + // (including the `where`) + if num_predicates > 0 && dropped_predicate_count == num_predicates { + let where_span = hir_generics + .where_clause + .span() + .expect("span of (nonempty) where clause should exist"); + // Extend the where clause back to the closing `>` of the + // generics, except for tuple struct, which have the `where` + // after the fields of the struct. + let full_where_span = + if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind { + where_span + } else { + hir_generics.span.shrink_to_hi().to(where_span) + }; + lint_spans.push(full_where_span); + } else { + lint_spans.extend(where_lint_spans); + } + + if !lint_spans.is_empty() { + cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| { + lint.build("outlives requirements can be inferred") + .multipart_suggestion( + if bound_count == 1 { + "remove this bound" + } else { + "remove these bounds" + }, + lint_spans + .into_iter() + .map(|span| (span, "".to_owned())) + .collect::<Vec<_>>(), + Applicability::MachineApplicable, + ) + .emit(); + }); + } + } + } +} + +declare_lint! { + pub INCOMPLETE_FEATURES, + Warn, + "incomplete features that may function improperly in some or all cases" +} + +declare_lint_pass!( + /// Check for used feature gates in `INCOMPLETE_FEATURES` in `librustc_feature/active.rs`. + IncompleteFeatures => [INCOMPLETE_FEATURES] +); + +impl EarlyLintPass for IncompleteFeatures { + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + let features = cx.sess.features_untracked(); + features + .declared_lang_features + .iter() + .map(|(name, span, _)| (name, span)) + .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span))) + .filter(|(name, _)| rustc_feature::INCOMPLETE_FEATURES.iter().any(|f| name == &f)) + .for_each(|(&name, &span)| { + cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| { + let mut builder = lint.build(&format!( + "the feature `{}` is incomplete and may not be safe to use \ + and/or cause compiler crashes", + name, + )); + if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) { + builder.note(&format!( + "see issue #{} <https://github.com/rust-lang/rust/issues/{}> \ + for more information", + n, n, + )); + } + builder.emit(); + }) + }); + } +} + +declare_lint! { + pub INVALID_VALUE, + Warn, + "an invalid value is being created (such as a NULL reference)" +} + +declare_lint_pass!(InvalidValue => [INVALID_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for InvalidValue { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + #[derive(Debug, Copy, Clone, PartialEq)] + enum InitKind { + Zeroed, + Uninit, + }; + + /// Information about why a type cannot be initialized this way. + /// Contains an error message and optionally a span to point at. + type InitError = (String, Option<Span>); + + /// Test if this constant is all-0. + fn is_zero(expr: &hir::Expr<'_>) -> bool { + use hir::ExprKind::*; + use rustc_ast::LitKind::*; + match &expr.kind { + Lit(lit) => { + if let Int(i, _) = lit.node { + i == 0 + } else { + false + } + } + Tup(tup) => tup.iter().all(is_zero), + _ => false, + } + } + + /// Determine if this expression is a "dangerous initialization". + fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<InitKind> { + // `transmute` is inside an anonymous module (the `extern` block?); + // `Invalid` represents the empty string and matches that. + // FIXME(#66075): use diagnostic items. Somehow, that does not seem to work + // on intrinsics right now. + const TRANSMUTE_PATH: &[Symbol] = + &[sym::core, sym::intrinsics, kw::Invalid, sym::transmute]; + + if let hir::ExprKind::Call(ref path_expr, ref args) = expr.kind { + // Find calls to `mem::{uninitialized,zeroed}` methods. + if let hir::ExprKind::Path(ref qpath) = path_expr.kind { + let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?; + + if cx.tcx.is_diagnostic_item(sym::mem_zeroed, def_id) { + return Some(InitKind::Zeroed); + } else if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, def_id) { + return Some(InitKind::Uninit); + } else if cx.match_def_path(def_id, TRANSMUTE_PATH) { + if is_zero(&args[0]) { + return Some(InitKind::Zeroed); + } + } + } + } else if let hir::ExprKind::MethodCall(_, _, ref args, _) = expr.kind { + // Find problematic calls to `MaybeUninit::assume_init`. + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + if cx.tcx.is_diagnostic_item(sym::assume_init, def_id) { + // This is a call to *some* method named `assume_init`. + // See if the `self` parameter is one of the dangerous constructors. + if let hir::ExprKind::Call(ref path_expr, _) = args[0].kind { + if let hir::ExprKind::Path(ref qpath) = path_expr.kind { + let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?; + + if cx.tcx.is_diagnostic_item(sym::maybe_uninit_zeroed, def_id) { + return Some(InitKind::Zeroed); + } else if cx.tcx.is_diagnostic_item(sym::maybe_uninit_uninit, def_id) { + return Some(InitKind::Uninit); + } + } + } + } + } + + None + } + + /// Test if this enum has several actually "existing" variants. + /// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist". + fn is_multi_variant(adt: &ty::AdtDef) -> bool { + // As an approximation, we only count dataless variants. Those are definitely inhabited. + let existing_variants = adt.variants.iter().filter(|v| v.fields.is_empty()).count(); + existing_variants > 1 + } + + /// Return `Some` only if we are sure this type does *not* + /// allow zero initialization. + fn ty_find_init_error<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + init: InitKind, + ) -> Option<InitError> { + use rustc_middle::ty::TyKind::*; + match ty.kind { + // Primitive types that don't like 0 as a value. + Ref(..) => Some(("references must be non-null".to_string(), None)), + Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)), + FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)), + Never => Some(("the `!` type has no valid value".to_string(), None)), + RawPtr(tm) if matches!(tm.ty.kind, Dynamic(..)) => + // raw ptr to dyn Trait + { + Some(("the vtable of a wide raw pointer must be non-null".to_string(), None)) + } + // Primitive types with other constraints. + Bool if init == InitKind::Uninit => { + Some(("booleans must be either `true` or `false`".to_string(), None)) + } + Char if init == InitKind::Uninit => { + Some(("characters must be a valid Unicode codepoint".to_string(), None)) + } + // Recurse and checks for some compound types. + Adt(adt_def, substs) if !adt_def.is_union() => { + // First check if this ADT has a layout attribute (like `NonNull` and friends). + use std::ops::Bound; + match tcx.layout_scalar_valid_range(adt_def.did) { + // We exploit here that `layout_scalar_valid_range` will never + // return `Bound::Excluded`. (And we have tests checking that we + // handle the attribute correctly.) + (Bound::Included(lo), _) if lo > 0 => { + return Some((format!("`{}` must be non-null", ty), None)); + } + (Bound::Included(_), _) | (_, Bound::Included(_)) + if init == InitKind::Uninit => + { + return Some(( + format!( + "`{}` must be initialized inside its custom valid range", + ty, + ), + None, + )); + } + _ => {} + } + // Now, recurse. + match adt_def.variants.len() { + 0 => Some(("enums with no variants have no valid value".to_string(), None)), + 1 => { + // Struct, or enum with exactly one variant. + // Proceed recursively, check all fields. + let variant = &adt_def.variants[VariantIdx::from_u32(0)]; + variant.fields.iter().find_map(|field| { + ty_find_init_error(tcx, field.ty(tcx, substs), init).map( + |(mut msg, span)| { + if span.is_none() { + // Point to this field, should be helpful for figuring + // out where the source of the error is. + let span = tcx.def_span(field.did); + write!( + &mut msg, + " (in this {} field)", + adt_def.descr() + ) + .unwrap(); + (msg, Some(span)) + } else { + // Just forward. + (msg, span) + } + }, + ) + }) + } + // Multi-variant enum. + _ => { + if init == InitKind::Uninit && is_multi_variant(adt_def) { + let span = tcx.def_span(adt_def.did); + Some(( + "enums have to be initialized to a variant".to_string(), + Some(span), + )) + } else { + // In principle, for zero-initialization we could figure out which variant corresponds + // to tag 0, and check that... but for now we just accept all zero-initializations. + None + } + } + } + } + Tuple(..) => { + // Proceed recursively, check all fields. + ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field, init)) + } + // Conservative fallback. + _ => None, + } + } + + if let Some(init) = is_dangerous_init(cx, expr) { + // This conjures an instance of a type out of nothing, + // using zeroed or uninitialized memory. + // We are extremely conservative with what we warn about. + let conjured_ty = cx.typeck_results().expr_ty(expr); + if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty, init) { + cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| { + let mut err = lint.build(&format!( + "the type `{}` does not permit {}", + conjured_ty, + match init { + InitKind::Zeroed => "zero-initialization", + InitKind::Uninit => "being left uninitialized", + }, + )); + err.span_label(expr.span, "this code causes undefined behavior when executed"); + err.span_label( + expr.span, + "help: use `MaybeUninit<T>` instead, \ + and only call `assume_init` after initialization is done", + ); + if let Some(span) = span { + err.span_note(span, &msg); + } else { + err.note(&msg); + } + err.emit(); + }); + } + } + } +} + +declare_lint! { + pub CLASHING_EXTERN_DECLARATIONS, + Warn, + "detects when an extern fn has been declared with the same name but different types" +} + +pub struct ClashingExternDeclarations { + seen_decls: FxHashMap<Symbol, HirId>, +} + +/// Differentiate between whether the name for an extern decl came from the link_name attribute or +/// just from declaration itself. This is important because we don't want to report clashes on +/// symbol name if they don't actually clash because one or the other links against a symbol with a +/// different name. +enum SymbolName { + /// The name of the symbol + the span of the annotation which introduced the link name. + Link(Symbol, Span), + /// No link name, so just the name of the symbol. + Normal(Symbol), +} + +impl SymbolName { + fn get_name(&self) -> Symbol { + match self { + SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, + } + } +} + +impl ClashingExternDeclarations { + crate fn new() -> Self { + ClashingExternDeclarations { seen_decls: FxHashMap::default() } + } + /// Insert a new foreign item into the seen set. If a symbol with the same name already exists + /// for the item, return its HirId without updating the set. + fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> { + let hid = fi.hir_id; + + let name = + &tcx.codegen_fn_attrs(tcx.hir().local_def_id(hid)).link_name.unwrap_or(fi.ident.name); + + if self.seen_decls.contains_key(name) { + // Avoid updating the map with the new entry when we do find a collision. We want to + // make sure we're always pointing to the first definition as the previous declaration. + // This lets us avoid emitting "knock-on" diagnostics. + Some(*self.seen_decls.get(name).unwrap()) + } else { + self.seen_decls.insert(*name, hid) + } + } + + /// Get the name of the symbol that's linked against for a given extern declaration. That is, + /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the + /// symbol's name. + fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName { + let did = tcx.hir().local_def_id(fi.hir_id); + if let Some((overridden_link_name, overridden_link_name_span)) = + tcx.codegen_fn_attrs(did).link_name.map(|overridden_link_name| { + // FIXME: Instead of searching through the attributes again to get span + // information, we could have codegen_fn_attrs also give span information back for + // where the attribute was defined. However, until this is found to be a + // bottleneck, this does just fine. + ( + overridden_link_name, + tcx.get_attrs(did.to_def_id()) + .iter() + .find(|at| tcx.sess.check_name(at, sym::link_name)) + .unwrap() + .span, + ) + }) + { + SymbolName::Link(overridden_link_name, overridden_link_name_span) + } else { + SymbolName::Normal(fi.ident.name) + } + } + + /// Checks whether two types are structurally the same enough that the declarations shouldn't + /// clash. We need this so we don't emit a lint when two modules both declare an extern struct, + /// with the same members (as the declarations shouldn't clash). + fn structurally_same_type<'tcx>( + cx: &LateContext<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: CItemKind, + ) -> bool { + fn structurally_same_type_impl<'tcx>( + seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, + cx: &LateContext<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: CItemKind, + ) -> bool { + debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b); + let tcx = cx.tcx; + + // Given a transparent newtype, reach through and grab the inner + // type unless the newtype makes the type non-null. + let non_transparent_ty = |ty: Ty<'tcx>| -> Ty<'tcx> { + let mut ty = ty; + loop { + if let ty::Adt(def, substs) = ty.kind { + let is_transparent = def.subst(tcx, substs).repr.transparent(); + let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, &def); + debug!( + "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", + ty, is_transparent, is_non_null + ); + if is_transparent && !is_non_null { + debug_assert!(def.variants.len() == 1); + let v = &def.variants[VariantIdx::new(0)]; + ty = v + .transparent_newtype_field(tcx) + .expect( + "single-variant transparent structure with zero-sized field", + ) + .ty(tcx, substs); + continue; + } + } + debug!("non_transparent_ty -> {:?}", ty); + return ty; + } + }; + + let a = non_transparent_ty(a); + let b = non_transparent_ty(b); + + if !seen_types.insert((a, b)) { + // We've encountered a cycle. There's no point going any further -- the types are + // structurally the same. + return true; + } + let tcx = cx.tcx; + if a == b || rustc_middle::ty::TyS::same_type(a, b) { + // All nominally-same types are structurally same, too. + true + } else { + // Do a full, depth-first comparison between the two. + use rustc_middle::ty::TyKind::*; + let a_kind = &a.kind; + let b_kind = &b.kind; + + let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> { + debug!("compare_layouts({:?}, {:?})", a, b); + let a_layout = &cx.layout_of(a)?.layout.abi; + let b_layout = &cx.layout_of(b)?.layout.abi; + debug!( + "comparing layouts: {:?} == {:?} = {}", + a_layout, + b_layout, + a_layout == b_layout + ); + Ok(a_layout == b_layout) + }; + + #[allow(rustc::usage_of_ty_tykind)] + let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| { + kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)) + }; + + ensure_sufficient_stack(|| { + match (a_kind, b_kind) { + (Adt(a_def, a_substs), Adt(b_def, b_substs)) => { + let a = a.subst(cx.tcx, a_substs); + let b = b.subst(cx.tcx, b_substs); + debug!("Comparing {:?} and {:?}", a, b); + + // We can immediately rule out these types as structurally same if + // their layouts differ. + match compare_layouts(a, b) { + Ok(false) => return false, + _ => (), // otherwise, continue onto the full, fields comparison + } + + // Grab a flattened representation of all fields. + let a_fields = a_def.variants.iter().flat_map(|v| v.fields.iter()); + let b_fields = b_def.variants.iter().flat_map(|v| v.fields.iter()); + + // Perform a structural comparison for each field. + a_fields.eq_by( + b_fields, + |&ty::FieldDef { did: a_did, .. }, + &ty::FieldDef { did: b_did, .. }| { + structurally_same_type_impl( + seen_types, + cx, + tcx.type_of(a_did), + tcx.type_of(b_did), + ckind, + ) + }, + ) + } + (Array(a_ty, a_const), Array(b_ty, b_const)) => { + // For arrays, we also check the constness of the type. + a_const.val == b_const.val + && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) + } + (Slice(a_ty), Slice(b_ty)) => { + structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) + } + (RawPtr(a_tymut), RawPtr(b_tymut)) => { + a_tymut.mutbl == b_tymut.mutbl + && structurally_same_type_impl( + seen_types, + cx, + &a_tymut.ty, + &b_tymut.ty, + ckind, + ) + } + (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { + // For structural sameness, we don't need the region to be same. + a_mut == b_mut + && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) + } + (FnDef(..), FnDef(..)) => { + let a_poly_sig = a.fn_sig(tcx); + let b_poly_sig = b.fn_sig(tcx); + + // As we don't compare regions, skip_binder is fine. + let a_sig = a_poly_sig.skip_binder(); + let b_sig = b_poly_sig.skip_binder(); + + (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) + == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) + && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { + structurally_same_type_impl(seen_types, cx, a, b, ckind) + }) + && structurally_same_type_impl( + seen_types, + cx, + a_sig.output(), + b_sig.output(), + ckind, + ) + } + (Tuple(a_substs), Tuple(b_substs)) => { + a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| { + structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) + }) + } + // For these, it's not quite as easy to define structural-sameness quite so easily. + // For the purposes of this lint, take the conservative approach and mark them as + // not structurally same. + (Dynamic(..), Dynamic(..)) + | (Error(..), Error(..)) + | (Closure(..), Closure(..)) + | (Generator(..), Generator(..)) + | (GeneratorWitness(..), GeneratorWitness(..)) + | (Projection(..), Projection(..)) + | (Opaque(..), Opaque(..)) => false, + + // These definitely should have been caught above. + (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), + + // An Adt and a primitive or pointer type. This can be FFI-safe if non-null + // enum layout optimisation is being applied. + (Adt(..), other_kind) | (other_kind, Adt(..)) + if is_primitive_or_pointer(other_kind) => + { + let (primitive, adt) = + if is_primitive_or_pointer(&a.kind) { (a, b) } else { (b, a) }; + if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) { + ty == primitive + } else { + compare_layouts(a, b).unwrap_or(false) + } + } + // Otherwise, just compare the layouts. This may fail to lint for some + // incompatible types, but at the very least, will stop reads into + // uninitialised memory. + _ => compare_layouts(a, b).unwrap_or(false), + } + }) + } + } + let mut seen_types = FxHashSet::default(); + structurally_same_type_impl(&mut seen_types, cx, a, b, ckind) + } +} + +impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]); + +impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) { + trace!("ClashingExternDeclarations: check_foreign_item: {:?}", this_fi); + if let ForeignItemKind::Fn(..) = this_fi.kind { + let tcx = *&cx.tcx; + if let Some(existing_hid) = self.insert(tcx, this_fi) { + let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid)); + let this_decl_ty = tcx.type_of(tcx.hir().local_def_id(this_fi.hir_id)); + debug!( + "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", + existing_hid, existing_decl_ty, this_fi.hir_id, this_decl_ty + ); + // Check that the declarations match. + if !Self::structurally_same_type( + cx, + existing_decl_ty, + this_decl_ty, + CItemKind::Declaration, + ) { + let orig_fi = tcx.hir().expect_foreign_item(existing_hid); + let orig = Self::name_of_extern_decl(tcx, orig_fi); + + // We want to ensure that we use spans for both decls that include where the + // name was defined, whether that was from the link_name attribute or not. + let get_relevant_span = + |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) { + SymbolName::Normal(_) => fi.span, + SymbolName::Link(_, annot_span) => fi.span.to(annot_span), + }; + // Finally, emit the diagnostic. + tcx.struct_span_lint_hir( + CLASHING_EXTERN_DECLARATIONS, + this_fi.hir_id, + get_relevant_span(this_fi), + |lint| { + let mut expected_str = DiagnosticStyledString::new(); + expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false); + let mut found_str = DiagnosticStyledString::new(); + found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true); + + lint.build(&format!( + "`{}` redeclare{} with a different signature", + this_fi.ident.name, + if orig.get_name() == this_fi.ident.name { + "d".to_string() + } else { + format!("s `{}`", orig.get_name()) + } + )) + .span_label( + get_relevant_span(orig_fi), + &format!("`{}` previously declared here", orig.get_name()), + ) + .span_label( + get_relevant_span(this_fi), + "this signature doesn't match the previous declaration", + ) + .note_expected_found(&"", expected_str, &"", found_str) + .emit() + }, + ); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs new file mode 100644 index 00000000000..a6784ffffcd --- /dev/null +++ b/compiler/rustc_lint/src/context.rs @@ -0,0 +1,862 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use self::TargetLint::*; + +use crate::levels::LintLevelsBuilder; +use crate::passes::{EarlyLintPassObject, LateLintPassObject}; +use rustc_ast as ast; +use rustc_ast::util::lev_distance::find_best_match_for_name; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::def_id::{CrateNum, DefId}; +use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; +use rustc_middle::lint::LintDiagnosticBuilder; +use rustc_middle::middle::privacy::AccessLevels; +use rustc_middle::middle::stability; +use rustc_middle::ty::layout::{LayoutError, TyAndLayout}; +use rustc_middle::ty::{self, print::Printer, subst::GenericArg, Ty, TyCtxt}; +use rustc_session::lint::{add_elided_lifetime_in_path_suggestion, BuiltinLintDiagnostics}; +use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; +use rustc_session::Session; +use rustc_span::{symbol::Symbol, MultiSpan, Span, DUMMY_SP}; +use rustc_target::abi::LayoutOf; + +use std::cell::Cell; +use std::slice; + +/// Information about the registered lints. +/// +/// This is basically the subset of `Context` that we can +/// build early in the compile pipeline. +pub struct LintStore { + /// Registered lints. + lints: Vec<&'static Lint>, + + /// Constructor functions for each variety of lint pass. + /// + /// These should only be called once, but since we want to avoid locks or + /// interior mutability, we don't enforce this (and lints should, in theory, + /// be compatible with being constructed more than once, though not + /// necessarily in a sane manner. This is safe though.) + pub pre_expansion_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>, + pub early_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>, + pub late_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>, + /// This is unique in that we construct them per-module, so not once. + pub late_module_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>, + + /// Lints indexed by name. + by_name: FxHashMap<String, TargetLint>, + + /// Map of registered lint groups to what lints they expand to. + lint_groups: FxHashMap<&'static str, LintGroup>, +} + +/// The target of the `by_name` map, which accounts for renaming/deprecation. +enum TargetLint { + /// A direct lint target + Id(LintId), + + /// Temporary renaming, used for easing migration pain; see #16545 + Renamed(String, LintId), + + /// Lint with this name existed previously, but has been removed/deprecated. + /// The string argument is the reason for removal. + Removed(String), +} + +pub enum FindLintError { + NotFound, + Removed, +} + +struct LintAlias { + name: &'static str, + /// Whether deprecation warnings should be suppressed for this alias. + silent: bool, +} + +struct LintGroup { + lint_ids: Vec<LintId>, + from_plugin: bool, + depr: Option<LintAlias>, +} + +pub enum CheckLintNameResult<'a> { + Ok(&'a [LintId]), + /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. + NoLint(Option<Symbol>), + /// The lint is either renamed or removed. This is the warning + /// message, and an optional new name (`None` if removed). + Warning(String, Option<String>), + /// The lint is from a tool. If the Option is None, then either + /// the lint does not exist in the tool or the code was not + /// compiled with the tool and therefore the lint was never + /// added to the `LintStore`. Otherwise the `LintId` will be + /// returned as if it where a rustc lint. + Tool(Result<&'a [LintId], (Option<&'a [LintId]>, String)>), +} + +impl LintStore { + pub fn new() -> LintStore { + LintStore { + lints: vec![], + pre_expansion_passes: vec![], + early_passes: vec![], + late_passes: vec![], + late_module_passes: vec![], + by_name: Default::default(), + lint_groups: Default::default(), + } + } + + pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] { + &self.lints + } + + pub fn get_lint_groups<'t>(&'t self) -> Vec<(&'static str, Vec<LintId>, bool)> { + self.lint_groups + .iter() + .filter(|(_, LintGroup { depr, .. })| { + // Don't display deprecated lint groups. + depr.is_none() + }) + .map(|(k, LintGroup { lint_ids, from_plugin, .. })| { + (*k, lint_ids.clone(), *from_plugin) + }) + .collect() + } + + pub fn register_early_pass( + &mut self, + pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.early_passes.push(Box::new(pass)); + } + + pub fn register_pre_expansion_pass( + &mut self, + pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.pre_expansion_passes.push(Box::new(pass)); + } + + pub fn register_late_pass( + &mut self, + pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.late_passes.push(Box::new(pass)); + } + + pub fn register_late_mod_pass( + &mut self, + pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.late_module_passes.push(Box::new(pass)); + } + + // Helper method for register_early/late_pass + pub fn register_lints(&mut self, lints: &[&'static Lint]) { + for lint in lints { + self.lints.push(lint); + + let id = LintId::of(lint); + if self.by_name.insert(lint.name_lower(), Id(id)).is_some() { + bug!("duplicate specification of lint {}", lint.name_lower()) + } + + if let Some(FutureIncompatibleInfo { edition, .. }) = lint.future_incompatible { + if let Some(edition) = edition { + self.lint_groups + .entry(edition.lint_name()) + .or_insert(LintGroup { + lint_ids: vec![], + from_plugin: lint.is_plugin, + depr: None, + }) + .lint_ids + .push(id); + } + + self.lint_groups + .entry("future_incompatible") + .or_insert(LintGroup { + lint_ids: vec![], + from_plugin: lint.is_plugin, + depr: None, + }) + .lint_ids + .push(id); + } + } + } + + pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) { + self.lint_groups.insert( + alias, + LintGroup { + lint_ids: vec![], + from_plugin: false, + depr: Some(LintAlias { name: lint_name, silent: true }), + }, + ); + } + + pub fn register_group( + &mut self, + from_plugin: bool, + name: &'static str, + deprecated_name: Option<&'static str>, + to: Vec<LintId>, + ) { + let new = self + .lint_groups + .insert(name, LintGroup { lint_ids: to, from_plugin, depr: None }) + .is_none(); + if let Some(deprecated) = deprecated_name { + self.lint_groups.insert( + deprecated, + LintGroup { + lint_ids: vec![], + from_plugin, + depr: Some(LintAlias { name, silent: false }), + }, + ); + } + + if !new { + bug!("duplicate specification of lint group {}", name); + } + } + + pub fn register_renamed(&mut self, old_name: &str, new_name: &str) { + let target = match self.by_name.get(new_name) { + Some(&Id(lint_id)) => lint_id, + _ => bug!("invalid lint renaming of {} to {}", old_name, new_name), + }; + self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target)); + } + + pub fn register_removed(&mut self, name: &str, reason: &str) { + self.by_name.insert(name.into(), Removed(reason.into())); + } + + pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> { + match self.by_name.get(lint_name) { + Some(&Id(lint_id)) => Ok(vec![lint_id]), + Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]), + Some(&Removed(_)) => Err(FindLintError::Removed), + None => loop { + return match self.lint_groups.get(lint_name) { + Some(LintGroup { lint_ids, depr, .. }) => { + if let Some(LintAlias { name, .. }) = depr { + lint_name = name; + continue; + } + Ok(lint_ids.clone()) + } + None => Err(FindLintError::Removed), + }; + }, + } + } + + /// Checks the validity of lint names derived from the command line + pub fn check_lint_name_cmdline(&self, sess: &Session, lint_name: &str, level: Level) { + let db = match self.check_lint_name(lint_name, None) { + CheckLintNameResult::Ok(_) => None, + CheckLintNameResult::Warning(ref msg, _) => Some(sess.struct_warn(msg)), + CheckLintNameResult::NoLint(suggestion) => { + let mut err = + struct_span_err!(sess, DUMMY_SP, E0602, "unknown lint: `{}`", lint_name); + + if let Some(suggestion) = suggestion { + err.help(&format!("did you mean: `{}`", suggestion)); + } + + Some(err) + } + CheckLintNameResult::Tool(result) => match result { + Err((Some(_), new_name)) => Some(sess.struct_warn(&format!( + "lint name `{}` is deprecated \ + and does not have an effect anymore. \ + Use: {}", + lint_name, new_name + ))), + _ => None, + }, + }; + + if let Some(mut db) = db { + let msg = format!( + "requested on the command line with `{} {}`", + match level { + Level::Allow => "-A", + Level::Warn => "-W", + Level::Deny => "-D", + Level::Forbid => "-F", + }, + lint_name + ); + db.note(&msg); + db.emit(); + } + } + + /// Checks the name of a lint for its existence, and whether it was + /// renamed or removed. Generates a DiagnosticBuilder containing a + /// warning for renamed and removed lints. This is over both lint + /// names from attributes and those passed on the command line. Since + /// it emits non-fatal warnings and there are *two* lint passes that + /// inspect attributes, this is only run from the late pass to avoid + /// printing duplicate warnings. + pub fn check_lint_name( + &self, + lint_name: &str, + tool_name: Option<Symbol>, + ) -> CheckLintNameResult<'_> { + let complete_name = if let Some(tool_name) = tool_name { + format!("{}::{}", tool_name, lint_name) + } else { + lint_name.to_string() + }; + // If the lint was scoped with `tool::` check if the tool lint exists + if tool_name.is_some() { + match self.by_name.get(&complete_name) { + None => match self.lint_groups.get(&*complete_name) { + None => return CheckLintNameResult::Tool(Err((None, String::new()))), + Some(LintGroup { lint_ids, .. }) => { + return CheckLintNameResult::Tool(Ok(&lint_ids)); + } + }, + Some(&Id(ref id)) => return CheckLintNameResult::Tool(Ok(slice::from_ref(id))), + // If the lint was registered as removed or renamed by the lint tool, we don't need + // to treat tool_lints and rustc lints different and can use the code below. + _ => {} + } + } + match self.by_name.get(&complete_name) { + Some(&Renamed(ref new_name, _)) => CheckLintNameResult::Warning( + format!("lint `{}` has been renamed to `{}`", complete_name, new_name), + Some(new_name.to_owned()), + ), + Some(&Removed(ref reason)) => CheckLintNameResult::Warning( + format!("lint `{}` has been removed: `{}`", complete_name, reason), + None, + ), + None => match self.lint_groups.get(&*complete_name) { + // If neither the lint, nor the lint group exists check if there is a `clippy::` + // variant of this lint + None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"), + Some(LintGroup { lint_ids, depr, .. }) => { + // Check if the lint group name is deprecated + if let Some(LintAlias { name, silent }) = depr { + let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); + return if *silent { + CheckLintNameResult::Ok(&lint_ids) + } else { + CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) + }; + } + CheckLintNameResult::Ok(&lint_ids) + } + }, + Some(&Id(ref id)) => CheckLintNameResult::Ok(slice::from_ref(id)), + } + } + + fn check_tool_name_for_backwards_compat( + &self, + lint_name: &str, + tool_name: &str, + ) -> CheckLintNameResult<'_> { + let complete_name = format!("{}::{}", tool_name, lint_name); + match self.by_name.get(&complete_name) { + None => match self.lint_groups.get(&*complete_name) { + // Now we are sure, that this lint exists nowhere + None => { + let symbols = + self.by_name.keys().map(|name| Symbol::intern(&name)).collect::<Vec<_>>(); + + let suggestion = find_best_match_for_name( + symbols.iter(), + Symbol::intern(&lint_name.to_lowercase()), + None, + ); + + CheckLintNameResult::NoLint(suggestion) + } + Some(LintGroup { lint_ids, depr, .. }) => { + // Reaching this would be weird, but let's cover this case anyway + if let Some(LintAlias { name, silent }) = depr { + let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); + return if *silent { + CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) + } else { + CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) + }; + } + CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) + } + }, + Some(&Id(ref id)) => { + CheckLintNameResult::Tool(Err((Some(slice::from_ref(id)), complete_name))) + } + _ => CheckLintNameResult::NoLint(None), + } + } +} + +/// Context for lint checking after type checking. +pub struct LateContext<'tcx> { + /// Type context we're checking in. + pub tcx: TyCtxt<'tcx>, + + /// Current body, or `None` if outside a body. + pub enclosing_body: Option<hir::BodyId>, + + /// Type-checking results for the current body. Access using the `typeck_results` + /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand. + // FIXME(eddyb) move all the code accessing internal fields like this, + // to this module, to avoid exposing it to lint logic. + pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>, + + /// Parameter environment for the item we are in. + pub param_env: ty::ParamEnv<'tcx>, + + /// Items accessible from the crate being checked. + pub access_levels: &'tcx AccessLevels, + + /// The store of registered lints and the lint levels. + pub lint_store: &'tcx LintStore, + + pub last_node_with_lint_attrs: hir::HirId, + + /// Generic type parameters in scope for the item we are in. + pub generics: Option<&'tcx hir::Generics<'tcx>>, + + /// We are only looking at one module + pub only_module: bool, +} + +/// Context for lint checking of the AST, after expansion, before lowering to +/// HIR. +pub struct EarlyContext<'a> { + /// Type context we're checking in. + pub sess: &'a Session, + + /// The crate being checked. + pub krate: &'a ast::Crate, + + pub builder: LintLevelsBuilder<'a>, + + /// The store of registered lints and the lint levels. + pub lint_store: &'a LintStore, + + pub buffered: LintBuffer, +} + +pub trait LintPassObject: Sized {} + +impl LintPassObject for EarlyLintPassObject {} + +impl LintPassObject for LateLintPassObject {} + +pub trait LintContext: Sized { + type PassObject: LintPassObject; + + fn sess(&self) -> &Session; + fn lints(&self) -> &LintStore; + + fn lookup_with_diagnostics( + &self, + lint: &'static Lint, + span: Option<impl Into<MultiSpan>>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + diagnostic: BuiltinLintDiagnostics, + ) { + self.lookup(lint, span, |lint| { + // We first generate a blank diagnostic. + let mut db = lint.build(""); + + // Now, set up surrounding context. + let sess = self.sess(); + match diagnostic { + BuiltinLintDiagnostics::Normal => (), + BuiltinLintDiagnostics::BareTraitObject(span, is_global) => { + let (sugg, app) = match sess.source_map().span_to_snippet(span) { + Ok(s) if is_global => { + (format!("dyn ({})", s), Applicability::MachineApplicable) + } + Ok(s) => (format!("dyn {}", s), Applicability::MachineApplicable), + Err(_) => ("dyn <type>".to_string(), Applicability::HasPlaceholders), + }; + db.span_suggestion(span, "use `dyn`", sugg, app); + } + BuiltinLintDiagnostics::AbsPathWithModule(span) => { + let (sugg, app) = match sess.source_map().span_to_snippet(span) { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = + if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable) + } + Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), + }; + db.span_suggestion(span, "use `crate`", sugg, app); + } + BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { + db.span_label( + span, + "names from parent modules are not accessible without an explicit import", + ); + } + BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths( + span_def, + ) => { + db.span_note(span_def, "the macro is defined here"); + } + BuiltinLintDiagnostics::ElidedLifetimesInPaths( + n, + path_span, + incl_angl_brckt, + insertion_span, + anon_lts, + ) => { + add_elided_lifetime_in_path_suggestion( + sess, + &mut db, + n, + path_span, + incl_angl_brckt, + insertion_span, + anon_lts, + ); + } + BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { + db.span_suggestion(span, ¬e, sugg, Applicability::MaybeIncorrect); + } + BuiltinLintDiagnostics::UnusedImports(message, replaces) => { + if !replaces.is_empty() { + db.tool_only_multipart_suggestion( + &message, + replaces, + Applicability::MachineApplicable, + ); + } + } + BuiltinLintDiagnostics::RedundantImport(spans, ident) => { + for (span, is_imported) in spans { + let introduced = if is_imported { "imported" } else { "defined" }; + db.span_label( + span, + format!("the item `{}` is already {} here", ident, introduced), + ); + } + } + BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { + stability::deprecation_suggestion(&mut db, "macro", suggestion, span) + } + BuiltinLintDiagnostics::UnusedDocComment(span) => { + db.span_label(span, "rustdoc does not generate documentation for macro invocations"); + db.help("to document an item produced by a macro, \ + the macro must produce the documentation as part of its expansion"); + } + } + // Rewrap `db`, and pass control to the user. + decorate(LintDiagnosticBuilder::new(db)); + }); + } + + // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to + // set the span in their `decorate` function (preferably using set_span). + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ); + + fn struct_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: S, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ) { + self.lookup(lint, Some(span), decorate); + } + /// Emit a lint at the appropriate level, with no associated span. + fn lint(&self, lint: &'static Lint, decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>)) { + self.lookup(lint, None as Option<Span>, decorate); + } +} + +impl<'a> EarlyContext<'a> { + pub fn new( + sess: &'a Session, + lint_store: &'a LintStore, + krate: &'a ast::Crate, + buffered: LintBuffer, + warn_about_weird_lints: bool, + ) -> EarlyContext<'a> { + EarlyContext { + sess, + krate, + lint_store, + builder: LintLevelsBuilder::new(sess, warn_about_weird_lints, lint_store), + buffered, + } + } +} + +impl LintContext for LateContext<'_> { + type PassObject = LateLintPassObject; + + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + &self.tcx.sess + } + + fn lints(&self) -> &LintStore { + &*self.lint_store + } + + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ) { + let hir_id = self.last_node_with_lint_attrs; + + match span { + Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate), + None => self.tcx.struct_lint_node(lint, hir_id, decorate), + } + } +} + +impl LintContext for EarlyContext<'_> { + type PassObject = EarlyLintPassObject; + + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + &self.sess + } + + fn lints(&self) -> &LintStore { + &*self.lint_store + } + + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ) { + self.builder.struct_lint(lint, span.map(|s| s.into()), decorate) + } +} + +impl<'tcx> LateContext<'tcx> { + /// Gets the type-checking results for the current body, + /// or `None` if outside a body. + pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> { + self.cached_typeck_results.get().or_else(|| { + self.enclosing_body.map(|body| { + let typeck_results = self.tcx.typeck_body(body); + self.cached_typeck_results.set(Some(typeck_results)); + typeck_results + }) + }) + } + + /// Gets the type-checking results for the current body. + /// As this will ICE if called outside bodies, only call when working with + /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). + #[track_caller] + pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { + self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body") + } + + /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable. + /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside + /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing. + pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res { + match *qpath { + hir::QPath::Resolved(_, ref path) => path.res, + hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self + .maybe_typeck_results() + .and_then(|typeck_results| typeck_results.type_dependent_def(id)) + .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)), + } + } + + pub fn current_lint_root(&self) -> hir::HirId { + self.last_node_with_lint_attrs + } + + /// Check if a `DefId`'s path matches the given absolute type path usage. + /// + /// Anonymous scopes such as `extern` imports are matched with `kw::Invalid`; + /// inherent `impl` blocks are matched with the name of the type. + /// + /// # Examples + /// + /// ```rust,ignore (no context or def id available) + /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) { + /// // The given `def_id` is that of an `Option` type + /// } + /// ``` + pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool { + let names = self.get_def_path(def_id); + + names.len() == path.len() && names.into_iter().zip(path.iter()).all(|(a, &b)| a == b) + } + + /// Gets the absolute path of `def_id` as a vector of `Symbol`. + /// + /// # Examples + /// + /// ```rust,ignore (no context or def id available) + /// let def_path = cx.get_def_path(def_id); + /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] { + /// // The given `def_id` is that of an `Option` type + /// } + /// ``` + pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> { + pub struct AbsolutePathPrinter<'tcx> { + pub tcx: TyCtxt<'tcx>, + } + + impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { + type Error = !; + + type Path = Vec<Symbol>; + type Region = (); + type Type = (); + type DynExistential = (); + type Const = (); + + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> { + Ok(()) + } + + fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> { + Ok(()) + } + + fn print_dyn_existential( + self, + _predicates: &'tcx ty::List<ty::ExistentialPredicate<'tcx>>, + ) -> Result<Self::DynExistential, Self::Error> { + Ok(()) + } + + fn print_const(self, _ct: &'tcx ty::Const<'tcx>) -> Result<Self::Const, Self::Error> { + Ok(()) + } + + fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> { + Ok(vec![self.tcx.original_crate_name(cnum)]) + } + + fn path_qualified( + self, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<Self::Path, Self::Error> { + if trait_ref.is_none() { + if let ty::Adt(def, substs) = self_ty.kind { + return self.print_def_path(def.did, substs); + } + } + + // This shouldn't ever be needed, but just in case: + Ok(vec![match trait_ref { + Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)), + None => Symbol::intern(&format!("<{}>", self_ty)), + }]) + } + + fn path_append_impl( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + _disambiguated_data: &DisambiguatedDefPathData, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<Self::Path, Self::Error> { + let mut path = print_prefix(self)?; + + // This shouldn't ever be needed, but just in case: + path.push(match trait_ref { + Some(trait_ref) => Symbol::intern(&format!( + "<impl {} for {}>", + trait_ref.print_only_trait_path(), + self_ty + )), + None => Symbol::intern(&format!("<impl {}>", self_ty)), + }); + + Ok(path) + } + + fn path_append( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + disambiguated_data: &DisambiguatedDefPathData, + ) -> Result<Self::Path, Self::Error> { + let mut path = print_prefix(self)?; + + // Skip `::{{constructor}}` on tuple/unit structs. + if let DefPathData::Ctor = disambiguated_data.data { + return Ok(path); + } + + path.push(disambiguated_data.data.as_symbol()); + Ok(path) + } + + fn path_generic_args( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + _args: &[GenericArg<'tcx>], + ) -> Result<Self::Path, Self::Error> { + print_prefix(self) + } + } + + AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap() + } +} + +impl<'tcx> LayoutOf for LateContext<'tcx> { + type Ty = Ty<'tcx>; + type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>; + + fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout { + self.tcx.layout_of(self.param_env.and(ty)) + } +} diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs new file mode 100644 index 00000000000..998676d44d2 --- /dev/null +++ b/compiler/rustc_lint/src/early.rs @@ -0,0 +1,381 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use crate::context::{EarlyContext, LintContext, LintStore}; +use crate::passes::{EarlyLintPass, EarlyLintPassObject}; +use rustc_ast as ast; +use rustc_ast::visit as ast_visit; +use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass}; +use rustc_session::Session; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +use std::slice; +use tracing::debug; + +macro_rules! run_early_pass { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +struct EarlyContextAndPass<'a, T: EarlyLintPass> { + context: EarlyContext<'a>, + pass: T, +} + +impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { + fn check_id(&mut self, id: ast::NodeId) { + for early_lint in self.context.buffered.take(id) { + let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint; + self.context.lookup_with_diagnostics( + lint_id.lint, + Some(span), + |lint| lint.build(&msg).emit(), + diagnostic, + ); + } + } + + /// Merge the lints specified by any lint attributes into the + /// current lint context, call the provided function, then reset the + /// lints in effect to their previous state. + fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F) + where + F: FnOnce(&mut Self), + { + let is_crate_node = id == ast::CRATE_NODE_ID; + let push = self.context.builder.push(attrs, &self.context.lint_store, is_crate_node); + self.check_id(id); + self.enter_attrs(attrs); + f(self); + self.exit_attrs(attrs); + self.context.builder.pop(push); + } + + fn enter_attrs(&mut self, attrs: &'a [ast::Attribute]) { + debug!("early context: enter_attrs({:?})", attrs); + run_early_pass!(self, enter_lint_attrs, attrs); + } + + fn exit_attrs(&mut self, attrs: &'a [ast::Attribute]) { + debug!("early context: exit_attrs({:?})", attrs); + run_early_pass!(self, exit_lint_attrs, attrs); + } +} + +impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> { + fn visit_param(&mut self, param: &'a ast::Param) { + self.with_lint_attrs(param.id, ¶m.attrs, |cx| { + run_early_pass!(cx, check_param, param); + ast_visit::walk_param(cx, param); + }); + } + + fn visit_item(&mut self, it: &'a ast::Item) { + self.with_lint_attrs(it.id, &it.attrs, |cx| { + run_early_pass!(cx, check_item, it); + ast_visit::walk_item(cx, it); + run_early_pass!(cx, check_item_post, it); + }) + } + + fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) { + self.with_lint_attrs(it.id, &it.attrs, |cx| { + run_early_pass!(cx, check_foreign_item, it); + ast_visit::walk_foreign_item(cx, it); + run_early_pass!(cx, check_foreign_item_post, it); + }) + } + + fn visit_pat(&mut self, p: &'a ast::Pat) { + run_early_pass!(self, check_pat, p); + self.check_id(p.id); + ast_visit::walk_pat(self, p); + run_early_pass!(self, check_pat_post, p); + } + + fn visit_anon_const(&mut self, c: &'a ast::AnonConst) { + run_early_pass!(self, check_anon_const, c); + ast_visit::walk_anon_const(self, c); + } + + fn visit_expr(&mut self, e: &'a ast::Expr) { + self.with_lint_attrs(e.id, &e.attrs, |cx| { + run_early_pass!(cx, check_expr, e); + ast_visit::walk_expr(cx, e); + }) + } + + fn visit_stmt(&mut self, s: &'a ast::Stmt) { + run_early_pass!(self, check_stmt, s); + self.check_id(s.id); + ast_visit::walk_stmt(self, s); + } + + fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) { + run_early_pass!(self, check_fn, fk, span, id); + self.check_id(id); + ast_visit::walk_fn(self, fk, span); + run_early_pass!(self, check_fn_post, fk, span, id); + } + + fn visit_variant_data(&mut self, s: &'a ast::VariantData) { + run_early_pass!(self, check_struct_def, s); + if let Some(ctor_hir_id) = s.ctor_id() { + self.check_id(ctor_hir_id); + } + ast_visit::walk_struct_def(self, s); + run_early_pass!(self, check_struct_def_post, s); + } + + fn visit_struct_field(&mut self, s: &'a ast::StructField) { + self.with_lint_attrs(s.id, &s.attrs, |cx| { + run_early_pass!(cx, check_struct_field, s); + ast_visit::walk_struct_field(cx, s); + }) + } + + fn visit_variant(&mut self, v: &'a ast::Variant) { + self.with_lint_attrs(v.id, &v.attrs, |cx| { + run_early_pass!(cx, check_variant, v); + ast_visit::walk_variant(cx, v); + run_early_pass!(cx, check_variant_post, v); + }) + } + + fn visit_ty(&mut self, t: &'a ast::Ty) { + run_early_pass!(self, check_ty, t); + self.check_id(t.id); + ast_visit::walk_ty(self, t); + } + + fn visit_ident(&mut self, ident: Ident) { + run_early_pass!(self, check_ident, ident); + } + + fn visit_mod(&mut self, m: &'a ast::Mod, s: Span, _a: &[ast::Attribute], n: ast::NodeId) { + run_early_pass!(self, check_mod, m, s, n); + self.check_id(n); + ast_visit::walk_mod(self, m); + run_early_pass!(self, check_mod_post, m, s, n); + } + + fn visit_local(&mut self, l: &'a ast::Local) { + self.with_lint_attrs(l.id, &l.attrs, |cx| { + run_early_pass!(cx, check_local, l); + ast_visit::walk_local(cx, l); + }) + } + + fn visit_block(&mut self, b: &'a ast::Block) { + run_early_pass!(self, check_block, b); + self.check_id(b.id); + ast_visit::walk_block(self, b); + run_early_pass!(self, check_block_post, b); + } + + fn visit_arm(&mut self, a: &'a ast::Arm) { + run_early_pass!(self, check_arm, a); + ast_visit::walk_arm(self, a); + } + + fn visit_expr_post(&mut self, e: &'a ast::Expr) { + run_early_pass!(self, check_expr_post, e); + } + + fn visit_generic_param(&mut self, param: &'a ast::GenericParam) { + run_early_pass!(self, check_generic_param, param); + ast_visit::walk_generic_param(self, param); + } + + fn visit_generics(&mut self, g: &'a ast::Generics) { + run_early_pass!(self, check_generics, g); + ast_visit::walk_generics(self, g); + } + + fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) { + run_early_pass!(self, check_where_predicate, p); + ast_visit::walk_where_predicate(self, p); + } + + fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) { + run_early_pass!(self, check_poly_trait_ref, t, m); + ast_visit::walk_poly_trait_ref(self, t, m); + } + + fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) { + self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt { + ast_visit::AssocCtxt::Trait => { + run_early_pass!(cx, check_trait_item, item); + ast_visit::walk_assoc_item(cx, item, ctxt); + run_early_pass!(cx, check_trait_item_post, item); + } + ast_visit::AssocCtxt::Impl => { + run_early_pass!(cx, check_impl_item, item); + ast_visit::walk_assoc_item(cx, item, ctxt); + run_early_pass!(cx, check_impl_item_post, item); + } + }); + } + + fn visit_lifetime(&mut self, lt: &'a ast::Lifetime) { + run_early_pass!(self, check_lifetime, lt); + self.check_id(lt.id); + } + + fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) { + run_early_pass!(self, check_path, p, id); + self.check_id(id); + ast_visit::walk_path(self, p); + } + + fn visit_attribute(&mut self, attr: &'a ast::Attribute) { + run_early_pass!(self, check_attribute, attr); + } + + fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) { + run_early_pass!(self, check_mac_def, mac, id); + self.check_id(id); + } + + fn visit_mac(&mut self, mac: &'a ast::MacCall) { + // FIXME(#54110): So, this setup isn't really right. I think + // that (a) the librustc_ast visitor ought to be doing this as + // part of `walk_mac`, and (b) we should be calling + // `visit_path`, *but* that would require a `NodeId`, and I + // want to get #53686 fixed quickly. -nmatsakis + ast_visit::walk_path(self, &mac.path); + + run_early_pass!(self, check_mac, mac); + } +} + +struct EarlyLintPassObjects<'a> { + lints: &'a mut [EarlyLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for EarlyLintPassObjects<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! expand_early_lint_pass_impl_methods { + ([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + for obj in self.lints.iter_mut() { + obj.$name(context, $($param),*); + } + })* + ) +} + +macro_rules! early_lint_pass_impl { + ([], [$($methods:tt)*]) => ( + impl EarlyLintPass for EarlyLintPassObjects<'_> { + expand_early_lint_pass_impl_methods!([$($methods)*]); + } + ) +} + +crate::early_lint_methods!(early_lint_pass_impl, []); + +fn early_lint_crate<T: EarlyLintPass>( + sess: &Session, + lint_store: &LintStore, + krate: &ast::Crate, + pass: T, + buffered: LintBuffer, + warn_about_weird_lints: bool, +) -> LintBuffer { + let mut cx = EarlyContextAndPass { + context: EarlyContext::new(sess, lint_store, krate, buffered, warn_about_weird_lints), + pass, + }; + + // Visit the whole crate. + cx.with_lint_attrs(ast::CRATE_NODE_ID, &krate.attrs, |cx| { + // since the root module isn't visited as an item (because it isn't an + // item), warn for it here. + run_early_pass!(cx, check_crate, krate); + + ast_visit::walk_crate(cx, krate); + + run_early_pass!(cx, check_crate_post, krate); + }); + cx.context.buffered +} + +pub fn check_ast_crate<T: EarlyLintPass>( + sess: &Session, + lint_store: &LintStore, + krate: &ast::Crate, + pre_expansion: bool, + lint_buffer: Option<LintBuffer>, + builtin_lints: T, +) { + let passes = + if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes }; + let mut passes: Vec<_> = passes.iter().map(|p| (p)()).collect(); + let mut buffered = lint_buffer.unwrap_or_default(); + + if !sess.opts.debugging_opts.no_interleave_lints { + buffered = + early_lint_crate(sess, lint_store, krate, builtin_lints, buffered, pre_expansion); + + if !passes.is_empty() { + buffered = early_lint_crate( + sess, + lint_store, + krate, + EarlyLintPassObjects { lints: &mut passes[..] }, + buffered, + pre_expansion, + ); + } + } else { + for pass in &mut passes { + buffered = + sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| { + early_lint_crate( + sess, + lint_store, + krate, + EarlyLintPassObjects { lints: slice::from_mut(pass) }, + buffered, + pre_expansion, + ) + }); + } + } + + // All of the buffered lints should have been emitted at this point. + // If not, that means that we somehow buffered a lint for a node id + // that was not lint-checked (perhaps it doesn't exist?). This is a bug. + // + // Rustdoc runs everybody-loops before the early lints and removes + // function bodies, so it's totally possible for linted + // node ids to not exist (e.g., macros defined within functions for the + // unused_macro lint) anymore. So we only run this check + // when we're not in rustdoc mode. (see issue #47639) + if !sess.opts.actually_rustdoc { + for (_id, lints) in buffered.map { + for early_lint in lints { + sess.delay_span_bug(early_lint.span, "failed to process buffered lint here"); + } + } + } +} diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs new file mode 100644 index 00000000000..100e555f299 --- /dev/null +++ b/compiler/rustc_lint/src/internal.rs @@ -0,0 +1,247 @@ +//! Some lints that are only useful in the compiler or crates that use compiler internals, such as +//! Clippy. + +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast::{Item, ItemKind}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::{GenericArg, HirId, MutTy, Mutability, Path, PathSegment, QPath, Ty, TyKind}; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::symbol::{sym, Ident, Symbol}; + +declare_tool_lint! { + pub rustc::DEFAULT_HASH_TYPES, + Allow, + "forbid HashMap and HashSet and suggest the FxHash* variants", + report_in_external_macro: true +} + +pub struct DefaultHashTypes { + map: FxHashMap<Symbol, Symbol>, +} + +impl DefaultHashTypes { + // we are allowed to use `HashMap` and `HashSet` as identifiers for implementing the lint itself + #[allow(rustc::default_hash_types)] + pub fn new() -> Self { + let mut map = FxHashMap::default(); + map.insert(sym::HashMap, sym::FxHashMap); + map.insert(sym::HashSet, sym::FxHashSet); + Self { map } + } +} + +impl_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]); + +impl EarlyLintPass for DefaultHashTypes { + fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) { + if let Some(replace) = self.map.get(&ident.name) { + cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, |lint| { + // FIXME: We can avoid a copy here. Would require us to take String instead of &str. + let msg = format!("Prefer {} over {}, it has better performance", replace, ident); + lint.build(&msg) + .span_suggestion( + ident.span, + "use", + replace.to_string(), + Applicability::MaybeIncorrect, // FxHashMap, ... needs another import + ) + .note(&format!( + "a `use rustc_data_structures::fx::{}` may be necessary", + replace + )) + .emit(); + }); + } + } +} + +declare_tool_lint! { + pub rustc::USAGE_OF_TY_TYKIND, + Allow, + "usage of `ty::TyKind` outside of the `ty::sty` module", + report_in_external_macro: true +} + +declare_tool_lint! { + pub rustc::TY_PASS_BY_REFERENCE, + Allow, + "passing `Ty` or `TyCtxt` by reference", + report_in_external_macro: true +} + +declare_tool_lint! { + pub rustc::USAGE_OF_QUALIFIED_TY, + Allow, + "using `ty::{Ty,TyCtxt}` instead of importing it", + report_in_external_macro: true +} + +declare_lint_pass!(TyTyKind => [ + USAGE_OF_TY_TYKIND, + TY_PASS_BY_REFERENCE, + USAGE_OF_QUALIFIED_TY, +]); + +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_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) { + match &ty.kind { + TyKind::Path(qpath) => { + if let QPath::Resolved(_, path) = qpath { + 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)) + .span_suggestion( + path.span, + "try using it unqualified", + t, + // The import probably needs to be changed + Applicability::MaybeIncorrect, + ) + .emit(); + }) + } + } + } + } + } + } + TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => { + if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) { + if cx.tcx.impl_trait_ref(impl_did).is_some() { + return; + } + } + if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) { + cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| { + lint.build(&format!("passing `{}` by reference", t)) + .span_suggestion( + ty.span, + "try passing by value", + t, + // Changing type of function argument + 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); + } + } + + false +} + +fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> { + if let TyKind::Path(qpath) = &ty.kind { + if let QPath::Resolved(_, path) = qpath { + let did = path.res.opt_def_id()?; + if cx.tcx.is_diagnostic_item(sym::Ty, did) { + return Some(format!("Ty{}", gen_args(path.segments.last().unwrap()))); + } else if cx.tcx.is_diagnostic_item(sym::TyCtxt, did) { + return Some(format!("TyCtxt{}", gen_args(path.segments.last().unwrap()))); + } + } + } + + None +} + +fn gen_args(segment: &PathSegment<'_>) -> String { + if let Some(args) = &segment.args { + let lifetimes = args + .args + .iter() + .filter_map(|arg| { + if let GenericArg::Lifetime(lt) = arg { + Some(lt.name.ident().to_string()) + } else { + None + } + }) + .collect::<Vec<_>>(); + + if !lifetimes.is_empty() { + return format!("<{}>", lifetimes.join(", ")); + } + } + + String::new() +} + +declare_tool_lint! { + pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO, + Allow, + "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros" +} + +declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]); + +impl EarlyLintPass for LintPassImpl { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Impl { of_trait: Some(lint_pass), .. } = &item.kind { + if let Some(last) = lint_pass.path.segments.last() { + if last.ident.name == sym::LintPass { + let expn_data = lint_pass.path.span.ctxt().outer_expn_data(); + let call_site = expn_data.call_site; + if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass) + && call_site.ctxt().outer_expn_data().kind + != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass) + { + cx.struct_span_lint( + LINT_PASS_IMPL_WITHOUT_MACRO, + lint_pass.path.span, + |lint| { + lint.build("implementing `LintPass` by hand") + .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead") + .emit(); + }, + ) + } + } + } + } + } +} diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs new file mode 100644 index 00000000000..a6c04fb0b4c --- /dev/null +++ b/compiler/rustc_lint/src/late.rs @@ -0,0 +1,499 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore}; +use rustc_ast as ast; +use rustc_ast::walk_list; +use rustc_data_structures::sync::{join, par_iter, ParallelIterator}; +use rustc_hir as hir; +use rustc_hir::def_id::{LocalDefId, LOCAL_CRATE}; +use rustc_hir::intravisit as hir_visit; +use rustc_hir::intravisit::Visitor; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::LintPass; +use rustc_span::symbol::Symbol; +use rustc_span::Span; + +use std::any::Any; +use std::cell::Cell; +use std::slice; +use tracing::debug; + +/// Extract the `LintStore` from the query context. +/// This function exists because we've erased `LintStore` as `dyn Any` in the context. +crate fn unerased_lint_store(tcx: TyCtxt<'_>) -> &LintStore { + let store: &dyn Any = &*tcx.lint_store; + store.downcast_ref().unwrap() +} + +macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> { + context: LateContext<'tcx>, + pass: T, +} + +impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> { + /// Merge the lints specified by any lint attributes into the + /// current lint context, call the provided function, then reset the + /// lints in effect to their previous state. + fn with_lint_attrs<F>(&mut self, id: hir::HirId, attrs: &'tcx [ast::Attribute], f: F) + where + F: FnOnce(&mut Self), + { + let prev = self.context.last_node_with_lint_attrs; + self.context.last_node_with_lint_attrs = id; + self.enter_attrs(attrs); + f(self); + self.exit_attrs(attrs); + self.context.last_node_with_lint_attrs = prev; + } + + fn with_param_env<F>(&mut self, id: hir::HirId, f: F) + where + F: FnOnce(&mut Self), + { + let old_param_env = self.context.param_env; + self.context.param_env = + self.context.tcx.param_env(self.context.tcx.hir().local_def_id(id)); + f(self); + self.context.param_env = old_param_env; + } + + fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) { + lint_callback!(self, check_mod, m, s, n); + hir_visit::walk_mod(self, m, n); + lint_callback!(self, check_mod_post, m, s, n); + } + + fn enter_attrs(&mut self, attrs: &'tcx [ast::Attribute]) { + debug!("late context: enter_attrs({:?})", attrs); + lint_callback!(self, enter_lint_attrs, attrs); + } + + fn exit_attrs(&mut self, attrs: &'tcx [ast::Attribute]) { + debug!("late context: exit_attrs({:?})", attrs); + lint_callback!(self, exit_lint_attrs, attrs); + } +} + +impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> { + type Map = Map<'tcx>; + + /// Because lints are scoped lexically, we want to walk nested + /// items in the context of the outer item, so enable + /// deep-walking. + fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap<Self::Map> { + hir_visit::NestedVisitorMap::All(self.context.tcx.hir()) + } + + fn visit_nested_body(&mut self, body_id: hir::BodyId) { + let old_enclosing_body = self.context.enclosing_body.replace(body_id); + let old_cached_typeck_results = self.context.cached_typeck_results.get(); + + // HACK(eddyb) avoid trashing `cached_typeck_results` when we're + // nested in `visit_fn`, which may have already resulted in them + // being queried. + if old_enclosing_body != Some(body_id) { + self.context.cached_typeck_results.set(None); + } + + let body = self.context.tcx.hir().body(body_id); + self.visit_body(body); + self.context.enclosing_body = old_enclosing_body; + + // See HACK comment above. + if old_enclosing_body != Some(body_id) { + self.context.cached_typeck_results.set(old_cached_typeck_results); + } + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.with_lint_attrs(param.hir_id, ¶m.attrs, |cx| { + lint_callback!(cx, check_param, param); + hir_visit::walk_param(cx, param); + }); + } + + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + lint_callback!(self, check_body, body); + hir_visit::walk_body(self, body); + lint_callback!(self, check_body_post, body); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = it.kind.generics(); + self.with_lint_attrs(it.hir_id, &it.attrs, |cx| { + cx.with_param_env(it.hir_id, |cx| { + lint_callback!(cx, check_item, it); + hir_visit::walk_item(cx, it); + lint_callback!(cx, check_item_post, it); + }); + }); + self.context.generics = generics; + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.with_lint_attrs(it.hir_id, &it.attrs, |cx| { + cx.with_param_env(it.hir_id, |cx| { + lint_callback!(cx, check_foreign_item, it); + hir_visit::walk_foreign_item(cx, it); + lint_callback!(cx, check_foreign_item_post, it); + }); + }) + } + + fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) { + lint_callback!(self, check_pat, p); + hir_visit::walk_pat(self, p); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + self.with_lint_attrs(e.hir_id, &e.attrs, |cx| { + lint_callback!(cx, check_expr, e); + hir_visit::walk_expr(cx, e); + lint_callback!(cx, check_expr_post, e); + }) + } + + fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) { + // statement attributes are actually just attributes on one of + // - item + // - local + // - expression + // so we keep track of lint levels there + lint_callback!(self, check_stmt, s); + hir_visit::walk_stmt(self, s); + } + + fn visit_fn( + &mut self, + fk: hir_visit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + span: Span, + id: hir::HirId, + ) { + // Wrap in typeck results here, not just in visit_nested_body, + // in order for `check_fn` to be able to use them. + let old_enclosing_body = self.context.enclosing_body.replace(body_id); + let old_cached_typeck_results = self.context.cached_typeck_results.take(); + let body = self.context.tcx.hir().body(body_id); + lint_callback!(self, check_fn, fk, decl, body, span, id); + hir_visit::walk_fn(self, fk, decl, body_id, span, id); + lint_callback!(self, check_fn_post, fk, decl, body, span, id); + self.context.enclosing_body = old_enclosing_body; + self.context.cached_typeck_results.set(old_cached_typeck_results); + } + + fn visit_variant_data( + &mut self, + s: &'tcx hir::VariantData<'tcx>, + _: Symbol, + _: &'tcx hir::Generics<'tcx>, + _: hir::HirId, + _: Span, + ) { + lint_callback!(self, check_struct_def, s); + hir_visit::walk_struct_def(self, s); + lint_callback!(self, check_struct_def_post, s); + } + + fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) { + self.with_lint_attrs(s.hir_id, &s.attrs, |cx| { + lint_callback!(cx, check_struct_field, s); + hir_visit::walk_struct_field(cx, s); + }) + } + + fn visit_variant( + &mut self, + v: &'tcx hir::Variant<'tcx>, + g: &'tcx hir::Generics<'tcx>, + item_id: hir::HirId, + ) { + self.with_lint_attrs(v.id, &v.attrs, |cx| { + lint_callback!(cx, check_variant, v); + hir_visit::walk_variant(cx, v, g, item_id); + lint_callback!(cx, check_variant_post, v); + }) + } + + fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) { + lint_callback!(self, check_ty, t); + hir_visit::walk_ty(self, t); + } + + fn visit_name(&mut self, sp: Span, name: Symbol) { + lint_callback!(self, check_name, sp, name); + } + + fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) { + if !self.context.only_module { + self.process_mod(m, s, n); + } + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { + self.with_lint_attrs(l.hir_id, &l.attrs, |cx| { + lint_callback!(cx, check_local, l); + hir_visit::walk_local(cx, l); + }) + } + + fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) { + lint_callback!(self, check_block, b); + hir_visit::walk_block(self, b); + lint_callback!(self, check_block_post, b); + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + lint_callback!(self, check_arm, a); + hir_visit::walk_arm(self, a); + } + + fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { + lint_callback!(self, check_generic_param, p); + hir_visit::walk_generic_param(self, p); + } + + fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) { + lint_callback!(self, check_generics, g); + hir_visit::walk_generics(self, g); + } + + fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) { + lint_callback!(self, check_where_predicate, p); + hir_visit::walk_where_predicate(self, p); + } + + fn visit_poly_trait_ref( + &mut self, + t: &'tcx hir::PolyTraitRef<'tcx>, + m: hir::TraitBoundModifier, + ) { + lint_callback!(self, check_poly_trait_ref, t, m); + hir_visit::walk_poly_trait_ref(self, t, m); + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = Some(&trait_item.generics); + self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |cx| { + cx.with_param_env(trait_item.hir_id, |cx| { + lint_callback!(cx, check_trait_item, trait_item); + hir_visit::walk_trait_item(cx, trait_item); + lint_callback!(cx, check_trait_item_post, trait_item); + }); + }); + self.context.generics = generics; + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = Some(&impl_item.generics); + self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |cx| { + cx.with_param_env(impl_item.hir_id, |cx| { + lint_callback!(cx, check_impl_item, impl_item); + hir_visit::walk_impl_item(cx, impl_item); + lint_callback!(cx, check_impl_item_post, impl_item); + }); + }); + self.context.generics = generics; + } + + fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) { + lint_callback!(self, check_lifetime, lt); + hir_visit::walk_lifetime(self, lt); + } + + fn visit_path(&mut self, p: &'tcx hir::Path<'tcx>, id: hir::HirId) { + lint_callback!(self, check_path, p, id); + hir_visit::walk_path(self, p); + } + + fn visit_attribute(&mut self, attr: &'tcx ast::Attribute) { + lint_callback!(self, check_attribute, attr); + } +} + +struct LateLintPassObjects<'a> { + lints: &'a mut [LateLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for LateLintPassObjects<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! expand_late_lint_pass_impl_methods { + ([$hir:tt], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &LateContext<$hir>, $($param: $arg),*) { + for obj in self.lints.iter_mut() { + obj.$name(context, $($param),*); + } + })* + ) +} + +macro_rules! late_lint_pass_impl { + ([], [$hir:tt], $methods:tt) => { + impl<$hir> LateLintPass<$hir> for LateLintPassObjects<'_> { + expand_late_lint_pass_impl_methods!([$hir], $methods); + } + }; +} + +crate::late_lint_methods!(late_lint_pass_impl, [], ['tcx]); + +fn late_lint_mod_pass<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalDefId, + pass: T, +) { + let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE); + + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + access_levels, + lint_store: unerased_lint_store(tcx), + last_node_with_lint_attrs: tcx.hir().local_def_id_to_hir_id(module_def_id), + generics: None, + only_module: true, + }; + + let mut cx = LateContextAndPass { context, pass }; + + let (module, span, hir_id) = tcx.hir().get_module(module_def_id); + cx.process_mod(module, span, hir_id); + + // Visit the crate attributes + if hir_id == hir::CRATE_HIR_ID { + walk_list!(cx, visit_attribute, tcx.hir().attrs(hir::CRATE_HIR_ID)); + } +} + +pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalDefId, + builtin_lints: T, +) { + if tcx.sess.opts.debugging_opts.no_interleave_lints { + // These passes runs in late_lint_crate with -Z no_interleave_lints + return; + } + + late_lint_mod_pass(tcx, module_def_id, builtin_lints); + + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect(); + + if !passes.is_empty() { + late_lint_mod_pass(tcx, module_def_id, LateLintPassObjects { lints: &mut passes[..] }); + } +} + +fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T) { + let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE); + + let krate = tcx.hir().krate(); + + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + access_levels, + lint_store: unerased_lint_store(tcx), + last_node_with_lint_attrs: hir::CRATE_HIR_ID, + generics: None, + only_module: false, + }; + + let mut cx = LateContextAndPass { context, pass }; + + // Visit the whole crate. + cx.with_lint_attrs(hir::CRATE_HIR_ID, &krate.item.attrs, |cx| { + // since the root module isn't visited as an item (because it isn't an + // item), warn for it here. + lint_callback!(cx, check_crate, krate); + + hir_visit::walk_crate(cx, krate); + + lint_callback!(cx, check_crate_post, krate); + }) +} + +fn late_lint_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, builtin_lints: T) { + let mut passes = unerased_lint_store(tcx).late_passes.iter().map(|p| (p)()).collect::<Vec<_>>(); + + if !tcx.sess.opts.debugging_opts.no_interleave_lints { + if !passes.is_empty() { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: &mut passes[..] }); + } + + late_lint_pass_crate(tcx, builtin_lints); + } else { + for pass in &mut passes { + tcx.sess.prof.extra_verbose_generic_activity("run_late_lint", pass.name()).run(|| { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) }); + }); + } + + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect(); + + for pass in &mut passes { + tcx.sess.prof.extra_verbose_generic_activity("run_late_module_lint", pass.name()).run( + || { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) }); + }, + ); + } + } +} + +/// Performs lint checking on a crate. +pub fn check_crate<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + builtin_lints: impl FnOnce() -> T + Send, +) { + join( + || { + tcx.sess.time("crate_lints", || { + // Run whole crate non-incremental lints + late_lint_crate(tcx, builtin_lints()); + }); + }, + || { + tcx.sess.time("module_lints", || { + // Run per-module lints + par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| { + tcx.ensure().lint_mod(tcx.hir().local_def_id(module)); + }); + }); + }, + ); +} diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs new file mode 100644 index 00000000000..48254dcee82 --- /dev/null +++ b/compiler/rustc_lint/src/levels.rs @@ -0,0 +1,576 @@ +use crate::context::{CheckLintNameResult, LintStore}; +use crate::late::unerased_lint_store; +use rustc_ast as ast; +use rustc_ast::attr; +use rustc_ast::unwrap_or; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; +use rustc_hir::{intravisit, HirId}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::LintDiagnosticBuilder; +use rustc_middle::lint::{struct_lint_level, LintLevelMap, LintLevelSets, LintSet, LintSource}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::{builtin, Level, Lint, LintId}; +use rustc_session::parse::feature_err; +use rustc_session::Session; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::{source_map::MultiSpan, Span, DUMMY_SP}; + +use std::cmp; + +fn lint_levels(tcx: TyCtxt<'_>, cnum: CrateNum) -> LintLevelMap { + assert_eq!(cnum, LOCAL_CRATE); + let store = unerased_lint_store(tcx); + let levels = LintLevelsBuilder::new(tcx.sess, false, &store); + let mut builder = LintLevelMapBuilder { levels, tcx, store }; + let krate = tcx.hir().krate(); + + let push = builder.levels.push(&krate.item.attrs, &store, true); + builder.levels.register_id(hir::CRATE_HIR_ID); + for macro_def in krate.exported_macros { + builder.levels.register_id(macro_def.hir_id); + } + intravisit::walk_crate(&mut builder, krate); + builder.levels.pop(push); + + builder.levels.build_map() +} + +pub struct LintLevelsBuilder<'s> { + sess: &'s Session, + sets: LintLevelSets, + id_to_set: FxHashMap<HirId, u32>, + cur: u32, + warn_about_weird_lints: bool, +} + +pub struct BuilderPush { + prev: u32, + pub changed: bool, +} + +impl<'s> LintLevelsBuilder<'s> { + pub fn new(sess: &'s Session, warn_about_weird_lints: bool, store: &LintStore) -> Self { + let mut builder = LintLevelsBuilder { + sess, + sets: LintLevelSets::new(), + cur: 0, + id_to_set: Default::default(), + warn_about_weird_lints, + }; + builder.process_command_line(sess, store); + assert_eq!(builder.sets.list.len(), 1); + builder + } + + fn process_command_line(&mut self, sess: &Session, store: &LintStore) { + let mut specs = FxHashMap::default(); + self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid); + + for &(ref lint_name, level) in &sess.opts.lint_opts { + store.check_lint_name_cmdline(sess, &lint_name, level); + + // If the cap is less than this specified level, e.g., if we've got + // `--cap-lints allow` but we've also got `-D foo` then we ignore + // this specification as the lint cap will set it to allow anyway. + let level = cmp::min(level, self.sets.lint_cap); + + let lint_flag_val = Symbol::intern(lint_name); + + let ids = match store.find_lints(&lint_name) { + Ok(ids) => ids, + Err(_) => continue, // errors handled in check_lint_name_cmdline above + }; + for id in ids { + self.check_gated_lint(id, DUMMY_SP); + let src = LintSource::CommandLine(lint_flag_val); + specs.insert(id, (level, src)); + } + } + + self.sets.list.push(LintSet::CommandLine { specs }); + } + + /// Pushes a list of AST lint attributes onto this context. + /// + /// This function will return a `BuilderPush` object which should be passed + /// to `pop` when this scope for the attributes provided is exited. + /// + /// This function will perform a number of tasks: + /// + /// * It'll validate all lint-related attributes in `attrs` + /// * It'll mark all lint-related attributes as used + /// * Lint levels will be updated based on the attributes provided + /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to + /// `#[allow]` + /// + /// Don't forget to call `pop`! + pub fn push( + &mut self, + attrs: &[ast::Attribute], + store: &LintStore, + is_crate_node: bool, + ) -> BuilderPush { + let mut specs = FxHashMap::default(); + let sess = self.sess; + let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input"); + for attr in attrs { + let level = match Level::from_symbol(attr.name_or_empty()) { + None => continue, + Some(lvl) => lvl, + }; + + let meta = unwrap_or!(attr.meta(), continue); + self.sess.mark_attr_used(attr); + + let mut metas = unwrap_or!(meta.meta_item_list(), continue); + + if metas.is_empty() { + // FIXME (#55112): issue unused-attributes lint for `#[level()]` + continue; + } + + // Before processing the lint names, look for a reason (RFC 2383) + // at the end. + let mut reason = None; + let tail_li = &metas[metas.len() - 1]; + if let Some(item) = tail_li.meta_item() { + match item.kind { + ast::MetaItemKind::Word => {} // actual lint names handled later + ast::MetaItemKind::NameValue(ref name_value) => { + if item.path == sym::reason { + // found reason, reslice meta list to exclude it + metas = &metas[0..metas.len() - 1]; + // FIXME (#55112): issue unused-attributes lint if we thereby + // don't have any lint names (`#[level(reason = "foo")]`) + if let ast::LitKind::Str(rationale, _) = name_value.kind { + if !self.sess.features_untracked().lint_reasons { + feature_err( + &self.sess.parse_sess, + sym::lint_reasons, + item.span, + "lint reasons are experimental", + ) + .emit(); + } + reason = Some(rationale); + } else { + bad_attr(name_value.span) + .span_label(name_value.span, "reason must be a string literal") + .emit(); + } + } else { + bad_attr(item.span) + .span_label(item.span, "bad attribute argument") + .emit(); + } + } + ast::MetaItemKind::List(_) => { + bad_attr(item.span).span_label(item.span, "bad attribute argument").emit(); + } + } + } + + for li in metas { + let meta_item = match li.meta_item() { + Some(meta_item) if meta_item.is_word() => meta_item, + _ => { + let sp = li.span(); + let mut err = bad_attr(sp); + let mut add_label = true; + if let Some(item) = li.meta_item() { + if let ast::MetaItemKind::NameValue(_) = item.kind { + if item.path == sym::reason { + err.span_label(sp, "reason in lint attribute must come last"); + add_label = false; + } + } + } + if add_label { + err.span_label(sp, "bad attribute argument"); + } + err.emit(); + continue; + } + }; + let tool_name = if meta_item.path.segments.len() > 1 { + let tool_ident = meta_item.path.segments[0].ident; + if !attr::is_known_lint_tool(tool_ident) { + struct_span_err!( + sess, + tool_ident.span, + E0710, + "an unknown tool name found in scoped lint: `{}`", + pprust::path_to_string(&meta_item.path), + ) + .emit(); + continue; + } + + Some(tool_ident.name) + } else { + None + }; + let name = meta_item.path.segments.last().expect("empty lint name").ident.name; + match store.check_lint_name(&name.as_str(), tool_name) { + CheckLintNameResult::Ok(ids) => { + let src = LintSource::Node(name, li.span(), reason); + for &id in ids { + self.check_gated_lint(id, attr.span); + specs.insert(id, (level, src)); + } + } + + CheckLintNameResult::Tool(result) => { + match result { + Ok(ids) => { + let complete_name = &format!("{}::{}", tool_name.unwrap(), name); + let src = LintSource::Node( + Symbol::intern(complete_name), + li.span(), + reason, + ); + for id in ids { + specs.insert(*id, (level, src)); + } + } + Err((Some(ids), new_lint_name)) => { + let lint = builtin::RENAMED_AND_REMOVED_LINTS; + let (lvl, src) = + self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess); + struct_lint_level( + self.sess, + lint, + lvl, + src, + Some(li.span().into()), + |lint| { + let msg = format!( + "lint name `{}` is deprecated \ + and may not have an effect in the future. \ + Also `cfg_attr(cargo-clippy)` won't be necessary anymore", + name + ); + lint.build(&msg) + .span_suggestion( + li.span(), + "change it to", + new_lint_name.to_string(), + Applicability::MachineApplicable, + ) + .emit(); + }, + ); + + let src = LintSource::Node( + Symbol::intern(&new_lint_name), + li.span(), + reason, + ); + for id in ids { + specs.insert(*id, (level, src)); + } + } + Err((None, _)) => { + // If Tool(Err(None, _)) is returned, then either the lint does not + // exist in the tool or the code was not compiled with the tool and + // therefore the lint was never added to the `LintStore`. To detect + // this is the responsibility of the lint tool. + } + } + } + + _ if !self.warn_about_weird_lints => {} + + CheckLintNameResult::Warning(msg, renamed) => { + let lint = builtin::RENAMED_AND_REMOVED_LINTS; + let (level, src) = + self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess); + struct_lint_level( + self.sess, + lint, + level, + src, + Some(li.span().into()), + |lint| { + let mut err = lint.build(&msg); + if let Some(new_name) = renamed { + err.span_suggestion( + li.span(), + "use the new name", + new_name, + Applicability::MachineApplicable, + ); + } + err.emit(); + }, + ); + } + CheckLintNameResult::NoLint(suggestion) => { + let lint = builtin::UNKNOWN_LINTS; + let (level, src) = + self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess); + struct_lint_level( + self.sess, + lint, + level, + src, + Some(li.span().into()), + |lint| { + let mut db = lint.build(&format!("unknown lint: `{}`", name)); + if let Some(suggestion) = suggestion { + db.span_suggestion( + li.span(), + "did you mean", + suggestion.to_string(), + Applicability::MachineApplicable, + ); + } + db.emit(); + }, + ); + } + } + } + } + + if !is_crate_node { + for (id, &(level, ref src)) in specs.iter() { + if !id.lint.crate_level_only { + continue; + } + + let (lint_attr_name, lint_attr_span) = match *src { + LintSource::Node(name, span, _) => (name, span), + _ => continue, + }; + + let lint = builtin::UNUSED_ATTRIBUTES; + let (lint_level, lint_src) = + self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess); + struct_lint_level( + self.sess, + lint, + lint_level, + lint_src, + Some(lint_attr_span.into()), + |lint| { + let mut db = lint.build(&format!( + "{}({}) is ignored unless specified at crate level", + level.as_str(), + lint_attr_name + )); + db.emit(); + }, + ); + // don't set a separate error for every lint in the group + break; + } + } + + for (id, &(level, ref src)) in specs.iter() { + if level == Level::Forbid { + continue; + } + let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) { + (Some(Level::Forbid), src) => src, + _ => continue, + }; + let forbidden_lint_name = match forbid_src { + LintSource::Default => id.to_string(), + LintSource::Node(name, _, _) => name.to_string(), + LintSource::CommandLine(name) => name.to_string(), + }; + let (lint_attr_name, lint_attr_span) = match *src { + LintSource::Node(name, span, _) => (name, span), + _ => continue, + }; + let mut diag_builder = struct_span_err!( + self.sess, + lint_attr_span, + E0453, + "{}({}) overruled by outer forbid({})", + level.as_str(), + lint_attr_name, + forbidden_lint_name + ); + diag_builder.span_label(lint_attr_span, "overruled by previous forbid"); + match forbid_src { + LintSource::Default => {} + LintSource::Node(_, forbid_source_span, reason) => { + diag_builder.span_label(forbid_source_span, "`forbid` level set here"); + if let Some(rationale) = reason { + diag_builder.note(&rationale.as_str()); + } + } + LintSource::CommandLine(_) => { + diag_builder.note("`forbid` lint level was set on command line"); + } + } + diag_builder.emit(); + // don't set a separate error for every lint in the group + break; + } + + let prev = self.cur; + if !specs.is_empty() { + self.cur = self.sets.list.len() as u32; + self.sets.list.push(LintSet::Node { specs, parent: prev }); + } + + BuilderPush { prev, changed: prev != self.cur } + } + + /// Checks if the lint is gated on a feature that is not enabled. + fn check_gated_lint(&self, lint_id: LintId, span: Span) { + if let Some(feature) = lint_id.lint.feature_gate { + if !self.sess.features_untracked().enabled(feature) { + feature_err( + &self.sess.parse_sess, + feature, + span, + &format!("the `{}` lint is unstable", lint_id.lint.name_lower()), + ) + .emit(); + } + } + } + + /// Called after `push` when the scope of a set of attributes are exited. + pub fn pop(&mut self, push: BuilderPush) { + self.cur = push.prev; + } + + /// Find the lint level for a lint. + pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintSource) { + self.sets.get_lint_level(lint, self.cur, None, self.sess) + } + + /// Used to emit a lint-related diagnostic based on the current state of + /// this lint context. + pub fn struct_lint( + &self, + lint: &'static Lint, + span: Option<MultiSpan>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>), + ) { + let (level, src) = self.lint_level(lint); + struct_lint_level(self.sess, lint, level, src, span, decorate) + } + + /// Registers the ID provided with the current set of lints stored in + /// this context. + pub fn register_id(&mut self, id: HirId) { + self.id_to_set.insert(id, self.cur); + } + + pub fn build(self) -> LintLevelSets { + self.sets + } + + pub fn build_map(self) -> LintLevelMap { + LintLevelMap { sets: self.sets, id_to_set: self.id_to_set } + } +} + +struct LintLevelMapBuilder<'a, 'tcx> { + levels: LintLevelsBuilder<'tcx>, + tcx: TyCtxt<'tcx>, + store: &'a LintStore, +} + +impl LintLevelMapBuilder<'_, '_> { + fn with_lint_attrs<F>(&mut self, id: hir::HirId, attrs: &[ast::Attribute], f: F) + where + F: FnOnce(&mut Self), + { + let is_crate_hir = id == hir::CRATE_HIR_ID; + let push = self.levels.push(attrs, self.store, is_crate_hir); + if push.changed { + self.levels.register_id(id); + } + f(self); + self.levels.pop(push); + } +} + +impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'_, 'tcx> { + type Map = Map<'tcx>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { + intravisit::NestedVisitorMap::All(self.tcx.hir()) + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.with_lint_attrs(param.hir_id, ¶m.attrs, |builder| { + intravisit::walk_param(builder, param); + }); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + self.with_lint_attrs(it.hir_id, &it.attrs, |builder| { + intravisit::walk_item(builder, it); + }); + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.with_lint_attrs(it.hir_id, &it.attrs, |builder| { + intravisit::walk_foreign_item(builder, it); + }) + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + self.with_lint_attrs(e.hir_id, &e.attrs, |builder| { + intravisit::walk_expr(builder, e); + }) + } + + fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) { + self.with_lint_attrs(s.hir_id, &s.attrs, |builder| { + intravisit::walk_struct_field(builder, s); + }) + } + + fn visit_variant( + &mut self, + v: &'tcx hir::Variant<'tcx>, + g: &'tcx hir::Generics<'tcx>, + item_id: hir::HirId, + ) { + self.with_lint_attrs(v.id, &v.attrs, |builder| { + intravisit::walk_variant(builder, v, g, item_id); + }) + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { + self.with_lint_attrs(l.hir_id, &l.attrs, |builder| { + intravisit::walk_local(builder, l); + }) + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + self.with_lint_attrs(a.hir_id, &a.attrs, |builder| { + intravisit::walk_arm(builder, a); + }) + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |builder| { + intravisit::walk_trait_item(builder, trait_item); + }); + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |builder| { + intravisit::walk_impl_item(builder, impl_item); + }); + } +} + +pub fn provide(providers: &mut Providers) { + providers.lint_levels = lint_levels; +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs new file mode 100644 index 00000000000..0a14b16e274 --- /dev/null +++ b/compiler/rustc_lint/src/lib.rs @@ -0,0 +1,464 @@ +//! Lints, aka compiler warnings. +//! +//! A 'lint' check is a kind of miscellaneous constraint that a user _might_ +//! want to enforce, but might reasonably want to permit as well, on a +//! module-by-module basis. They contrast with static constraints enforced by +//! other phases of the compiler, which are generally required to hold in order +//! to compile the program at all. +//! +//! Most lints can be written as [LintPass] instances. These run after +//! all other analyses. The `LintPass`es built into rustc are defined +//! within [rustc_session::lint::builtin], +//! which has further comments on how to add such a lint. +//! rustc can also load user-defined lint plugins via the plugin mechanism. +//! +//! Some of rustc's lints are defined elsewhere in the compiler and work by +//! calling `add_lint()` on the overall `Session` object. This works when +//! it happens before the main lint pass, which emits the lints stored by +//! `add_lint()`. To emit lints after the main lint pass (from codegen, for +//! example) requires more effort. See `emit_lint` and `GatherNodeLevels` +//! in `context.rs`. +//! +//! Some code also exists in [rustc_session::lint], [rustc_middle::lint]. +//! +//! ## Note +//! +//! This API is completely unstable and subject to change. + +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")] +#![cfg_attr(test, feature(test))] +#![feature(bool_to_option)] +#![feature(box_syntax)] +#![feature(crate_visibility_modifier)] +#![feature(iter_order_by)] +#![feature(never_type)] +#![feature(nll)] +#![feature(or_patterns)] +#![recursion_limit = "256"] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; + +mod array_into_iter; +pub mod builtin; +mod context; +mod early; +mod internal; +mod late; +mod levels; +mod non_ascii_idents; +mod nonstandard_style; +mod passes; +mod redundant_semicolon; +mod types; +mod unused; + +use rustc_ast as ast; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::{ + BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS, + EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, MISSING_DOC_CODE_EXAMPLES, + PRIVATE_DOC_TESTS, +}; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +use array_into_iter::ArrayIntoIter; +use builtin::*; +use internal::*; +use non_ascii_idents::*; +use nonstandard_style::*; +use redundant_semicolon::*; +use types::*; +use unused::*; + +/// Useful for other parts of the compiler / Clippy. +pub use builtin::SoftLints; +pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore}; +pub use early::check_ast_crate; +pub use late::check_crate; +pub use passes::{EarlyLintPass, LateLintPass}; +pub use rustc_session::lint::Level::{self, *}; +pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId}; +pub use rustc_session::lint::{LintArray, LintPass}; + +pub fn provide(providers: &mut Providers) { + levels::provide(providers); + *providers = Providers { lint_mod, ..*providers }; +} + +fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); +} + +macro_rules! pre_expansion_lint_passes { + ($macro:path, $args:tt) => { + $macro!($args, [KeywordIdents: KeywordIdents,]); + }; +} + +macro_rules! early_lint_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + UnusedParens: UnusedParens, + UnusedBraces: UnusedBraces, + UnusedImportBraces: UnusedImportBraces, + UnsafeCode: UnsafeCode, + AnonymousParameters: AnonymousParameters, + EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), + NonCamelCaseTypes: NonCamelCaseTypes, + DeprecatedAttr: DeprecatedAttr::new(), + WhileTrue: WhileTrue, + NonAsciiIdents: NonAsciiIdents, + IncompleteFeatures: IncompleteFeatures, + RedundantSemicolons: RedundantSemicolons, + UnusedDocComment: UnusedDocComment, + ] + ); + }; +} + +macro_rules! declare_combined_early_pass { + ([$name:ident], $passes:tt) => ( + early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]); + ) +} + +pre_expansion_lint_passes!(declare_combined_early_pass, [BuiltinCombinedPreExpansionLintPass]); +early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]); + +macro_rules! late_lint_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + // FIXME: Look into regression when this is used as a module lint + // May Depend on constants elsewhere + UnusedBrokenConst: UnusedBrokenConst, + // Uses attr::is_used which is untracked, can't be an incremental module pass. + UnusedAttributes: UnusedAttributes::new(), + // Needs to run after UnusedAttributes as it marks all `feature` attributes as used. + UnstableFeatures: UnstableFeatures, + // Tracks state across modules + UnnameableTestItems: UnnameableTestItems::new(), + // Tracks attributes of parents + MissingDoc: MissingDoc::new(), + // Depends on access levels + // FIXME: Turn the computation of types which implement Debug into a query + // and change this to a module lint pass + MissingDebugImplementations: MissingDebugImplementations::default(), + ArrayIntoIter: ArrayIntoIter, + ClashingExternDeclarations: ClashingExternDeclarations::new(), + ] + ); + }; +} + +macro_rules! late_lint_mod_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + HardwiredLints: HardwiredLints, + ImproperCTypesDeclarations: ImproperCTypesDeclarations, + ImproperCTypesDefinitions: ImproperCTypesDefinitions, + VariantSizeDifferences: VariantSizeDifferences, + BoxPointers: BoxPointers, + PathStatements: PathStatements, + // Depends on referenced function signatures in expressions + UnusedResults: UnusedResults, + NonUpperCaseGlobals: NonUpperCaseGlobals, + NonShorthandFieldPatterns: NonShorthandFieldPatterns, + UnusedAllocation: UnusedAllocation, + // Depends on types used in type definitions + MissingCopyImplementations: MissingCopyImplementations, + // Depends on referenced function signatures in expressions + MutableTransmutes: MutableTransmutes, + TypeAliasBounds: TypeAliasBounds, + TrivialConstraints: TrivialConstraints, + TypeLimits: TypeLimits::new(), + NonSnakeCase: NonSnakeCase, + InvalidNoMangleItems: InvalidNoMangleItems, + // Depends on access levels + UnreachablePub: UnreachablePub, + ExplicitOutlivesRequirements: ExplicitOutlivesRequirements, + InvalidValue: InvalidValue, + ] + ); + }; +} + +macro_rules! declare_combined_late_pass { + ([$v:vis $name:ident], $passes:tt) => ( + late_lint_methods!(declare_combined_late_lint_pass, [$v $name, $passes], ['tcx]); + ) +} + +// FIXME: Make a separate lint type which do not require typeck tables +late_lint_passes!(declare_combined_late_pass, [pub BuiltinCombinedLateLintPass]); + +late_lint_mod_passes!(declare_combined_late_pass, [BuiltinCombinedModuleLateLintPass]); + +pub fn new_lint_store(no_interleave_lints: bool, internal_lints: bool) -> LintStore { + let mut lint_store = LintStore::new(); + + register_builtins(&mut lint_store, no_interleave_lints); + if internal_lints { + register_internals(&mut lint_store); + } + + lint_store +} + +/// Tell the `LintStore` about all the built-in lints (the ones +/// defined in this crate and the ones defined in +/// `rustc_session::lint::builtin`). +fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) { + macro_rules! add_lint_group { + ($name:expr, $($lint:ident),*) => ( + store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]); + ) + } + + macro_rules! register_pass { + ($method:ident, $ty:ident, $constructor:expr) => { + store.register_lints(&$ty::get_lints()); + store.$method(|| box $constructor); + }; + } + + macro_rules! register_passes { + ($method:ident, [$($passes:ident: $constructor:expr,)*]) => ( + $( + register_pass!($method, $passes, $constructor); + )* + ) + } + + if no_interleave_lints { + pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); + early_lint_passes!(register_passes, register_early_pass); + late_lint_passes!(register_passes, register_late_pass); + late_lint_mod_passes!(register_passes, register_late_mod_pass); + } else { + store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); + store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); + store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); + store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); + } + + add_lint_group!( + "nonstandard_style", + NON_CAMEL_CASE_TYPES, + NON_SNAKE_CASE, + NON_UPPER_CASE_GLOBALS + ); + + add_lint_group!( + "unused", + UNUSED_IMPORTS, + UNUSED_VARIABLES, + UNUSED_ASSIGNMENTS, + DEAD_CODE, + UNUSED_MUT, + UNREACHABLE_CODE, + UNREACHABLE_PATTERNS, + OVERLAPPING_PATTERNS, + UNUSED_MUST_USE, + UNUSED_UNSAFE, + PATH_STATEMENTS, + UNUSED_ATTRIBUTES, + UNUSED_MACROS, + UNUSED_ALLOCATION, + UNUSED_DOC_COMMENTS, + UNUSED_EXTERN_CRATES, + UNUSED_FEATURES, + UNUSED_LABELS, + UNUSED_PARENS, + UNUSED_BRACES, + REDUNDANT_SEMICOLONS + ); + + add_lint_group!( + "rust_2018_idioms", + BARE_TRAIT_OBJECTS, + UNUSED_EXTERN_CRATES, + ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + ELIDED_LIFETIMES_IN_PATHS, + EXPLICIT_OUTLIVES_REQUIREMENTS // FIXME(#52665, #47816) not always applicable and not all + // macros are ready for this yet. + // UNREACHABLE_PUB, + + // FIXME macro crates are not up for this yet, too much + // breakage is seen if we try to encourage this lint. + // MACRO_USE_EXTERN_CRATE + ); + + add_lint_group!( + "rustdoc", + BROKEN_INTRA_DOC_LINKS, + INVALID_CODEBLOCK_ATTRIBUTES, + MISSING_DOC_CODE_EXAMPLES, + PRIVATE_DOC_TESTS + ); + + // Register renamed and removed lints. + store.register_renamed("single_use_lifetime", "single_use_lifetimes"); + store.register_renamed("elided_lifetime_in_path", "elided_lifetimes_in_paths"); + store.register_renamed("bare_trait_object", "bare_trait_objects"); + store.register_renamed("unstable_name_collision", "unstable_name_collisions"); + store.register_renamed("unused_doc_comment", "unused_doc_comments"); + store.register_renamed("async_idents", "keyword_idents"); + store.register_renamed("exceeding_bitshifts", "arithmetic_overflow"); + store.register_renamed("redundant_semicolon", "redundant_semicolons"); + store.register_renamed("intra_doc_link_resolution_failure", "broken_intra_doc_links"); + store.register_removed("unknown_features", "replaced by an error"); + store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate"); + store.register_removed("negate_unsigned", "cast a signed value instead"); + store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok"); + // Register lint group aliases. + store.register_group_alias("nonstandard_style", "bad_style"); + // This was renamed to `raw_pointer_derive`, which was then removed, + // so it is also considered removed. + store.register_removed("raw_pointer_deriving", "using derive with raw pointers is ok"); + store.register_removed("drop_with_repr_extern", "drop flags have been removed"); + store.register_removed("fat_ptr_transmutes", "was accidentally removed back in 2014"); + store.register_removed("deprecated_attr", "use `deprecated` instead"); + store.register_removed( + "transmute_from_fn_item_types", + "always cast functions before transmuting them", + ); + store.register_removed( + "hr_lifetime_in_assoc_type", + "converted into hard error, see issue #33685 \ + <https://github.com/rust-lang/rust/issues/33685> for more information", + ); + store.register_removed( + "inaccessible_extern_crate", + "converted into hard error, see issue #36886 \ + <https://github.com/rust-lang/rust/issues/36886> for more information", + ); + store.register_removed( + "super_or_self_in_global_path", + "converted into hard error, see issue #36888 \ + <https://github.com/rust-lang/rust/issues/36888> for more information", + ); + store.register_removed( + "overlapping_inherent_impls", + "converted into hard error, see issue #36889 \ + <https://github.com/rust-lang/rust/issues/36889> for more information", + ); + store.register_removed( + "illegal_floating_point_constant_pattern", + "converted into hard error, see issue #36890 \ + <https://github.com/rust-lang/rust/issues/36890> for more information", + ); + store.register_removed( + "illegal_struct_or_enum_constant_pattern", + "converted into hard error, see issue #36891 \ + <https://github.com/rust-lang/rust/issues/36891> for more information", + ); + store.register_removed( + "lifetime_underscore", + "converted into hard error, see issue #36892 \ + <https://github.com/rust-lang/rust/issues/36892> for more information", + ); + store.register_removed( + "extra_requirement_in_impl", + "converted into hard error, see issue #37166 \ + <https://github.com/rust-lang/rust/issues/37166> for more information", + ); + store.register_removed( + "legacy_imports", + "converted into hard error, see issue #38260 \ + <https://github.com/rust-lang/rust/issues/38260> for more information", + ); + store.register_removed( + "coerce_never", + "converted into hard error, see issue #48950 \ + <https://github.com/rust-lang/rust/issues/48950> for more information", + ); + store.register_removed( + "resolve_trait_on_defaulted_unit", + "converted into hard error, see issue #48950 \ + <https://github.com/rust-lang/rust/issues/48950> for more information", + ); + store.register_removed( + "private_no_mangle_fns", + "no longer a warning, `#[no_mangle]` functions always exported", + ); + store.register_removed( + "private_no_mangle_statics", + "no longer a warning, `#[no_mangle]` statics always exported", + ); + store.register_removed("bad_repr", "replaced with a generic attribute input check"); + store.register_removed( + "duplicate_matcher_binding_name", + "converted into hard error, see issue #57742 \ + <https://github.com/rust-lang/rust/issues/57742> for more information", + ); + store.register_removed( + "incoherent_fundamental_impls", + "converted into hard error, see issue #46205 \ + <https://github.com/rust-lang/rust/issues/46205> for more information", + ); + store.register_removed( + "legacy_constructor_visibility", + "converted into hard error, see issue #39207 \ + <https://github.com/rust-lang/rust/issues/39207> for more information", + ); + store.register_removed( + "legacy_directory_ownership", + "converted into hard error, see issue #37872 \ + <https://github.com/rust-lang/rust/issues/37872> for more information", + ); + store.register_removed( + "safe_extern_statics", + "converted into hard error, see issue #36247 \ + <https://github.com/rust-lang/rust/issues/36247> for more information", + ); + store.register_removed( + "parenthesized_params_in_types_and_modules", + "converted into hard error, see issue #42238 \ + <https://github.com/rust-lang/rust/issues/42238> for more information", + ); + store.register_removed( + "duplicate_macro_exports", + "converted into hard error, see issue #35896 \ + <https://github.com/rust-lang/rust/issues/35896> for more information", + ); + store.register_removed( + "nested_impl_trait", + "converted into hard error, see issue #59014 \ + <https://github.com/rust-lang/rust/issues/59014> for more information", + ); + store.register_removed("plugin_as_library", "plugins have been deprecated and retired"); +} + +fn register_internals(store: &mut LintStore) { + store.register_lints(&DefaultHashTypes::get_lints()); + store.register_early_pass(|| box DefaultHashTypes::new()); + store.register_lints(&LintPassImpl::get_lints()); + store.register_early_pass(|| box LintPassImpl); + store.register_lints(&TyTyKind::get_lints()); + store.register_late_pass(|| box TyTyKind); + store.register_group( + false, + "rustc::internal", + None, + vec![ + LintId::of(DEFAULT_HASH_TYPES), + LintId::of(USAGE_OF_TY_TYKIND), + LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO), + LintId::of(TY_PASS_BY_REFERENCE), + LintId::of(USAGE_OF_QUALIFIED_TY), + ], + ); +} diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs new file mode 100644 index 00000000000..2f0b2a8d680 --- /dev/null +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -0,0 +1,240 @@ +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast as ast; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::symbol::Symbol; + +declare_lint! { + pub NON_ASCII_IDENTS, + Allow, + "detects non-ASCII identifiers", + crate_level_only +} + +declare_lint! { + pub UNCOMMON_CODEPOINTS, + Warn, + "detects uncommon Unicode codepoints in identifiers", + crate_level_only +} + +declare_lint! { + pub CONFUSABLE_IDENTS, + Warn, + "detects visually confusable pairs between identifiers", + crate_level_only +} + +declare_lint! { + pub MIXED_SCRIPT_CONFUSABLES, + Warn, + "detects Unicode scripts whose mixed script confusables codepoints are solely used", + crate_level_only +} + +declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]); + +impl EarlyLintPass for NonAsciiIdents { + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + use rustc_session::lint::Level; + use rustc_span::Span; + use std::collections::BTreeMap; + use unicode_security::GeneralSecurityProfile; + + let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow; + let check_uncommon_codepoints = + cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow; + let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow; + let check_mixed_script_confusables = + cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow; + + if !check_non_ascii_idents + && !check_uncommon_codepoints + && !check_confusable_idents + && !check_mixed_script_confusables + { + return; + } + + let mut has_non_ascii_idents = false; + let symbols = cx.sess.parse_sess.symbol_gallery.symbols.lock(); + + // Sort by `Span` so that error messages make sense with respect to the + // order of identifier locations in the code. + let mut symbols: Vec<_> = symbols.iter().collect(); + symbols.sort_by_key(|k| k.1); + + for (symbol, &sp) in symbols.iter() { + let symbol_str = symbol.as_str(); + if symbol_str.is_ascii() { + continue; + } + has_non_ascii_idents = true; + cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| { + lint.build("identifier contains non-ASCII characters").emit() + }); + if check_uncommon_codepoints + && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed) + { + cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| { + lint.build("identifier contains uncommon Unicode codepoints").emit() + }) + } + } + + if has_non_ascii_idents && check_confusable_idents { + let mut skeleton_map: FxHashMap<Symbol, (Symbol, Span, bool)> = + FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default()); + let mut skeleton_buf = String::new(); + + for (&symbol, &sp) in symbols.iter() { + use unicode_security::confusable_detection::skeleton; + + let symbol_str = symbol.as_str(); + let is_ascii = symbol_str.is_ascii(); + + // Get the skeleton as a `Symbol`. + skeleton_buf.clear(); + skeleton_buf.extend(skeleton(&symbol_str)); + let skeleton_sym = if *symbol_str == *skeleton_buf { + symbol + } else { + Symbol::intern(&skeleton_buf) + }; + + skeleton_map + .entry(skeleton_sym) + .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| { + if !*existing_is_ascii || !is_ascii { + cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| { + lint.build(&format!( + "identifier pair considered confusable between `{}` and `{}`", + existing_symbol.as_str(), + symbol.as_str() + )) + .span_label( + *existing_span, + "this is where the previous identifier occurred", + ) + .emit(); + }); + } + if *existing_is_ascii && !is_ascii { + *existing_symbol = symbol; + *existing_span = sp; + *existing_is_ascii = is_ascii; + } + }) + .or_insert((symbol, sp, is_ascii)); + } + } + + if has_non_ascii_idents && check_mixed_script_confusables { + use unicode_security::is_potential_mixed_script_confusable_char; + use unicode_security::mixed_script::AugmentedScriptSet; + + #[derive(Clone)] + enum ScriptSetUsage { + Suspicious(Vec<char>, Span), + Verified, + } + + let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> = + FxHashMap::default(); + let latin_augmented_script_set = AugmentedScriptSet::for_char('A'); + script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified); + + let mut has_suspicous = false; + for (symbol, &sp) in symbols.iter() { + let symbol_str = symbol.as_str(); + for ch in symbol_str.chars() { + if ch.is_ascii() { + // all ascii characters are covered by exception. + continue; + } + if !GeneralSecurityProfile::identifier_allowed(ch) { + // this character is covered by `uncommon_codepoints` lint. + continue; + } + let augmented_script_set = AugmentedScriptSet::for_char(ch); + script_states + .entry(augmented_script_set) + .and_modify(|existing_state| { + if let ScriptSetUsage::Suspicious(ch_list, _) = existing_state { + if is_potential_mixed_script_confusable_char(ch) { + ch_list.push(ch); + } else { + *existing_state = ScriptSetUsage::Verified; + } + } + }) + .or_insert_with(|| { + if !is_potential_mixed_script_confusable_char(ch) { + ScriptSetUsage::Verified + } else { + has_suspicous = true; + ScriptSetUsage::Suspicious(vec![ch], sp) + } + }); + } + } + + if has_suspicous { + let verified_augmented_script_sets = script_states + .iter() + .flat_map(|(k, v)| match v { + ScriptSetUsage::Verified => Some(*k), + _ => None, + }) + .collect::<Vec<_>>(); + + // we're sorting the output here. + let mut lint_reports: BTreeMap<(Span, Vec<char>), AugmentedScriptSet> = + BTreeMap::new(); + + 'outerloop: for (augment_script_set, usage) in script_states { + let (mut ch_list, sp) = match usage { + ScriptSetUsage::Verified => continue, + ScriptSetUsage::Suspicious(ch_list, sp) => (ch_list, sp), + }; + + if augment_script_set.is_all() { + continue; + } + + for existing in verified_augmented_script_sets.iter() { + if existing.is_all() { + continue; + } + let mut intersect = *existing; + intersect.intersect_with(augment_script_set); + if !intersect.is_empty() && !intersect.is_all() { + continue 'outerloop; + } + } + + ch_list.sort(); + ch_list.dedup(); + lint_reports.insert((sp, ch_list), augment_script_set); + } + + for ((sp, ch_list), script_set) in lint_reports { + cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| { + let message = format!( + "The usage of Script Group `{}` in this crate consists solely of mixed script confusables", + script_set); + let mut note = "The usage includes ".to_string(); + for (idx, ch) in ch_list.into_iter().enumerate() { + if idx != 0 { + note += ", "; + } + let char_info = format!("'{}' (U+{:04X})", ch, ch as u32); + note += &char_info; + } + note += "."; + lint.build(&message).note(¬e).note("Please recheck to make sure their usages are indeed what you want.").emit() + }); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs new file mode 100644 index 00000000000..f23e8c5e208 --- /dev/null +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -0,0 +1,456 @@ +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{GenericParamKind, PatKind}; +use rustc_middle::ty; +use rustc_span::symbol::sym; +use rustc_span::{symbol::Ident, BytePos, Span}; +use rustc_target::spec::abi::Abi; + +#[derive(PartialEq)] +pub enum MethodLateContext { + TraitAutoImpl, + TraitImpl, + PlainImpl, +} + +pub fn method_context(cx: &LateContext<'_>, id: hir::HirId) -> MethodLateContext { + let def_id = cx.tcx.hir().local_def_id(id); + let item = cx.tcx.associated_item(def_id); + match item.container { + ty::TraitContainer(..) => MethodLateContext::TraitAutoImpl, + ty::ImplContainer(cid) => match cx.tcx.impl_trait_ref(cid) { + Some(_) => MethodLateContext::TraitImpl, + None => MethodLateContext::PlainImpl, + }, + } +} + +declare_lint! { + pub NON_CAMEL_CASE_TYPES, + Warn, + "types, variants, traits and type parameters should have camel case names" +} + +declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]); + +fn char_has_case(c: char) -> bool { + c.is_lowercase() || c.is_uppercase() +} + +fn is_camel_case(name: &str) -> bool { + let name = name.trim_matches('_'); + if name.is_empty() { + return true; + } + + // start with a non-lowercase letter rather than non-uppercase + // ones (some scripts don't have a concept of upper/lowercase) + !name.chars().next().unwrap().is_lowercase() + && !name.contains("__") + && !name.chars().collect::<Vec<_>>().windows(2).any(|pair| { + // contains a capitalisable character followed by, or preceded by, an underscore + char_has_case(pair[0]) && pair[1] == '_' || char_has_case(pair[1]) && pair[0] == '_' + }) +} + +fn to_camel_case(s: &str) -> String { + s.trim_matches('_') + .split('_') + .filter(|component| !component.is_empty()) + .map(|component| { + let mut camel_cased_component = String::new(); + + let mut new_word = true; + let mut prev_is_lower_case = true; + + for c in component.chars() { + // Preserve the case if an uppercase letter follows a lowercase letter, so that + // `camelCase` is converted to `CamelCase`. + if prev_is_lower_case && c.is_uppercase() { + new_word = true; + } + + if new_word { + camel_cased_component.push_str(&c.to_uppercase().to_string()); + } else { + camel_cased_component.push_str(&c.to_lowercase().to_string()); + } + + prev_is_lower_case = c.is_lowercase(); + new_word = false; + } + + camel_cased_component + }) + .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| { + // separate two components with an underscore if their boundary cannot + // be distinguished using a uppercase/lowercase case distinction + let join = if let Some(prev) = prev { + let l = prev.chars().last().unwrap(); + let f = next.chars().next().unwrap(); + !char_has_case(l) && !char_has_case(f) + } else { + false + }; + (acc + if join { "_" } else { "" } + &next, Some(next)) + }) + .0 +} + +impl NonCamelCaseTypes { + fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) { + let name = &ident.name.as_str(); + + if !is_camel_case(name) { + cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| { + let msg = format!("{} `{}` should have an upper camel case name", sort, name); + lint.build(&msg) + .span_suggestion( + ident.span, + "convert the identifier to upper camel case", + to_camel_case(name), + Applicability::MaybeIncorrect, + ) + .emit() + }) + } + } +} + +impl EarlyLintPass for NonCamelCaseTypes { + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { + let has_repr_c = it + .attrs + .iter() + .any(|attr| attr::find_repr_attrs(&cx.sess, attr).contains(&attr::ReprC)); + + if has_repr_c { + return; + } + + match it.kind { + ast::ItemKind::TyAlias(..) + | ast::ItemKind::Enum(..) + | ast::ItemKind::Struct(..) + | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident), + ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident), + _ => (), + } + } + + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::TyAlias(..) = it.kind { + self.check_case(cx, "associated type", &it.ident); + } + } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) { + self.check_case(cx, "variant", &v.ident); + } + + fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) { + if let ast::GenericParamKind::Type { .. } = param.kind { + self.check_case(cx, "type parameter", ¶m.ident); + } + } +} + +declare_lint! { + pub NON_SNAKE_CASE, + Warn, + "variables, methods, functions, lifetime parameters and modules should have snake case names" +} + +declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]); + +impl NonSnakeCase { + fn to_snake_case(mut str: &str) -> String { + let mut words = vec![]; + // Preserve leading underscores + str = str.trim_start_matches(|c: char| { + if c == '_' { + words.push(String::new()); + true + } else { + false + } + }); + for s in str.split('_') { + let mut last_upper = false; + let mut buf = String::new(); + if s.is_empty() { + continue; + } + for ch in s.chars() { + if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper { + words.push(buf); + buf = String::new(); + } + last_upper = ch.is_uppercase(); + buf.extend(ch.to_lowercase()); + } + words.push(buf); + } + words.join("_") + } + + /// Checks if a given identifier is snake case, and reports a diagnostic if not. + fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) { + fn is_snake_case(ident: &str) -> bool { + if ident.is_empty() { + return true; + } + let ident = ident.trim_start_matches('\''); + let ident = ident.trim_matches('_'); + + let mut allow_underscore = true; + ident.chars().all(|c| { + allow_underscore = match c { + '_' if !allow_underscore => return false, + '_' => false, + // It would be more obvious to use `c.is_lowercase()`, + // but some characters do not have a lowercase form + c if !c.is_uppercase() => true, + _ => return false, + }; + true + }) + } + + let name = &ident.name.as_str(); + + if !is_snake_case(name) { + cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| { + let sc = NonSnakeCase::to_snake_case(name); + let msg = format!("{} `{}` should have a snake case name", sort, name); + let mut err = lint.build(&msg); + // We have a valid span in almost all cases, but we don't have one when linting a crate + // name provided via the command line. + if !ident.span.is_dummy() { + err.span_suggestion( + ident.span, + "convert the identifier to snake case", + sc, + Applicability::MaybeIncorrect, + ); + } else { + err.help(&format!("convert the identifier to snake case: `{}`", sc)); + } + + err.emit(); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { + fn check_mod( + &mut self, + cx: &LateContext<'_>, + _: &'tcx hir::Mod<'tcx>, + _: Span, + id: hir::HirId, + ) { + if id != hir::CRATE_HIR_ID { + return; + } + + let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name { + Some(Ident::from_str(name)) + } else { + cx.sess() + .find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name) + .and_then(|attr| attr.meta()) + .and_then(|meta| { + meta.name_value_literal().and_then(|lit| { + if let ast::LitKind::Str(name, ..) = lit.kind { + // Discard the double quotes surrounding the literal. + let sp = cx + .sess() + .source_map() + .span_to_snippet(lit.span) + .ok() + .and_then(|snippet| { + let left = snippet.find('"')?; + let right = + snippet.rfind('"').map(|pos| snippet.len() - pos)?; + + Some( + lit.span + .with_lo(lit.span.lo() + BytePos(left as u32 + 1)) + .with_hi(lit.span.hi() - BytePos(right as u32)), + ) + }) + .unwrap_or_else(|| lit.span); + + Some(Ident::new(name, sp)) + } else { + None + } + }) + }) + }; + + if let Some(ident) = &crate_ident { + self.check_snake_case(cx, "crate", ident); + } + } + + fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) { + if let GenericParamKind::Lifetime { .. } = param.kind { + self.check_snake_case(cx, "lifetime", ¶m.name.ident()); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fk: FnKind<'_>, + _: &hir::FnDecl<'_>, + _: &hir::Body<'_>, + _: Span, + id: hir::HirId, + ) { + match &fk { + FnKind::Method(ident, ..) => match method_context(cx, id) { + MethodLateContext::PlainImpl => { + self.check_snake_case(cx, "method", ident); + } + MethodLateContext::TraitAutoImpl => { + self.check_snake_case(cx, "trait method", ident); + } + _ => (), + }, + FnKind::ItemFn(ident, _, header, _, attrs) => { + // Skip foreign-ABI #[no_mangle] functions (Issue #31924) + if header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) { + return; + } + self.check_snake_case(cx, "function", ident); + } + FnKind::Closure(_) => (), + } + } + + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if let hir::ItemKind::Mod(_) = it.kind { + self.check_snake_case(cx, "module", &it.ident); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) { + if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind { + self.check_snake_case(cx, "trait method", &item.ident); + for param_name in pnames { + self.check_snake_case(cx, "variable", param_name); + } + } + } + + fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) { + if let &PatKind::Binding(_, hid, ident, _) = &p.kind { + if let hir::Node::Pat(parent_pat) = cx.tcx.hir().get(cx.tcx.hir().get_parent_node(hid)) + { + if let PatKind::Struct(_, field_pats, _) = &parent_pat.kind { + for field in field_pats.iter() { + if field.ident != ident { + // Only check if a new name has been introduced, to avoid warning + // on both the struct definition and this pattern. + self.check_snake_case(cx, "variable", &ident); + } + } + return; + } + } + self.check_snake_case(cx, "variable", &ident); + } + } + + fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) { + for sf in s.fields() { + self.check_snake_case(cx, "structure field", &sf.ident); + } + } +} + +declare_lint! { + pub NON_UPPER_CASE_GLOBALS, + Warn, + "static constants should have uppercase identifiers" +} + +declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]); + +impl NonUpperCaseGlobals { + fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) { + let name = &ident.name.as_str(); + if name.chars().any(|c| c.is_lowercase()) { + cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| { + let uc = NonSnakeCase::to_snake_case(&name).to_uppercase(); + lint.build(&format!("{} `{}` should have an upper case name", sort, name)) + .span_suggestion( + ident.span, + "convert the identifier to upper case", + uc, + Applicability::MaybeIncorrect, + ) + .emit(); + }) + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Static(..) if !cx.sess().contains_name(&it.attrs, sym::no_mangle) => { + NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident); + } + hir::ItemKind::Const(..) => { + NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident); + } + _ => {} + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) { + if let hir::TraitItemKind::Const(..) = ti.kind { + NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) { + if let hir::ImplItemKind::Const(..) = ii.kind { + NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident); + } + } + + fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) { + // Lint for constants that look like binding identifiers (#7526) + if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind { + if let Res::Def(DefKind::Const, _) = path.res { + if path.segments.len() == 1 { + NonUpperCaseGlobals::check_upper_case( + cx, + "constant in pattern", + &path.segments[0].ident, + ); + } + } + } + } + + fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) { + if let GenericParamKind::Const { .. } = param.kind { + NonUpperCaseGlobals::check_upper_case(cx, "const parameter", ¶m.name.ident()); + } + } +} + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_lint/src/nonstandard_style/tests.rs b/compiler/rustc_lint/src/nonstandard_style/tests.rs new file mode 100644 index 00000000000..39c525b8623 --- /dev/null +++ b/compiler/rustc_lint/src/nonstandard_style/tests.rs @@ -0,0 +1,21 @@ +use super::{is_camel_case, to_camel_case}; + +#[test] +fn camel_case() { + assert!(!is_camel_case("userData")); + assert_eq!(to_camel_case("userData"), "UserData"); + + assert!(is_camel_case("X86_64")); + + assert!(!is_camel_case("X86__64")); + assert_eq!(to_camel_case("X86__64"), "X86_64"); + + assert!(!is_camel_case("Abc_123")); + assert_eq!(to_camel_case("Abc_123"), "Abc123"); + + assert!(!is_camel_case("A1_b2_c3")); + assert_eq!(to_camel_case("A1_b2_c3"), "A1B2C3"); + + assert!(!is_camel_case("ONE_TWO_THREE")); + assert_eq!(to_camel_case("ONE_TWO_THREE"), "OneTwoThree"); +} diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs new file mode 100644 index 00000000000..159286c13a4 --- /dev/null +++ b/compiler/rustc_lint/src/passes.rs @@ -0,0 +1,285 @@ +use crate::context::{EarlyContext, LateContext}; + +use rustc_ast as ast; +use rustc_data_structures::sync; +use rustc_hir as hir; +use rustc_session::lint::builtin::HardwiredLints; +use rustc_session::lint::LintPass; +use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::Span; + +#[macro_export] +macro_rules! late_lint_methods { + ($macro:path, $args:tt, [$hir:tt]) => ( + $macro!($args, [$hir], [ + fn check_param(a: &$hir hir::Param<$hir>); + fn check_body(a: &$hir hir::Body<$hir>); + fn check_body_post(a: &$hir hir::Body<$hir>); + fn check_name(a: Span, b: Symbol); + fn check_crate(a: &$hir hir::Crate<$hir>); + fn check_crate_post(a: &$hir hir::Crate<$hir>); + fn check_mod(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId); + fn check_mod_post(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId); + fn check_foreign_item(a: &$hir hir::ForeignItem<$hir>); + fn check_foreign_item_post(a: &$hir hir::ForeignItem<$hir>); + fn check_item(a: &$hir hir::Item<$hir>); + fn check_item_post(a: &$hir hir::Item<$hir>); + fn check_local(a: &$hir hir::Local<$hir>); + fn check_block(a: &$hir hir::Block<$hir>); + fn check_block_post(a: &$hir hir::Block<$hir>); + fn check_stmt(a: &$hir hir::Stmt<$hir>); + fn check_arm(a: &$hir hir::Arm<$hir>); + fn check_pat(a: &$hir hir::Pat<$hir>); + fn check_expr(a: &$hir hir::Expr<$hir>); + fn check_expr_post(a: &$hir hir::Expr<$hir>); + fn check_ty(a: &$hir hir::Ty<$hir>); + fn check_generic_param(a: &$hir hir::GenericParam<$hir>); + fn check_generics(a: &$hir hir::Generics<$hir>); + fn check_where_predicate(a: &$hir hir::WherePredicate<$hir>); + fn check_poly_trait_ref(a: &$hir hir::PolyTraitRef<$hir>, b: hir::TraitBoundModifier); + fn check_fn( + a: rustc_hir::intravisit::FnKind<$hir>, + b: &$hir hir::FnDecl<$hir>, + c: &$hir hir::Body<$hir>, + d: Span, + e: hir::HirId); + fn check_fn_post( + a: rustc_hir::intravisit::FnKind<$hir>, + b: &$hir hir::FnDecl<$hir>, + c: &$hir hir::Body<$hir>, + d: Span, + e: hir::HirId + ); + fn check_trait_item(a: &$hir hir::TraitItem<$hir>); + fn check_trait_item_post(a: &$hir hir::TraitItem<$hir>); + fn check_impl_item(a: &$hir hir::ImplItem<$hir>); + fn check_impl_item_post(a: &$hir hir::ImplItem<$hir>); + fn check_struct_def(a: &$hir hir::VariantData<$hir>); + fn check_struct_def_post(a: &$hir hir::VariantData<$hir>); + fn check_struct_field(a: &$hir hir::StructField<$hir>); + fn check_variant(a: &$hir hir::Variant<$hir>); + fn check_variant_post(a: &$hir hir::Variant<$hir>); + fn check_lifetime(a: &$hir hir::Lifetime); + fn check_path(a: &$hir hir::Path<$hir>, b: hir::HirId); + fn check_attribute(a: &$hir ast::Attribute); + + /// Called when entering a syntax node that can have lint attributes such + /// as `#[allow(...)]`. Called with *all* the attributes of that node. + fn enter_lint_attrs(a: &$hir [ast::Attribute]); + + /// Counterpart to `enter_lint_attrs`. + fn exit_lint_attrs(a: &$hir [ast::Attribute]); + ]); + ) +} + +/// Trait for types providing lint checks. +/// +/// Each `check` method checks a single syntax node, and should not +/// invoke methods recursively (unlike `Visitor`). By default they +/// do nothing. +// +// FIXME: eliminate the duplication with `Visitor`. But this also +// contains a few lint-specific methods with no equivalent in `Visitor`. + +macro_rules! expand_lint_pass_methods { + ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* + ) +} + +macro_rules! declare_late_lint_pass { + ([], [$hir:tt], [$($methods:tt)*]) => ( + pub trait LateLintPass<$hir>: LintPass { + expand_lint_pass_methods!(&LateContext<$hir>, [$($methods)*]); + } + ) +} + +late_lint_methods!(declare_late_lint_pass, [], ['tcx]); + +impl LateLintPass<'_> for HardwiredLints {} + +#[macro_export] +macro_rules! expand_combined_late_lint_pass_method { + ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$passes.$name $params;)* + }) +} + +#[macro_export] +macro_rules! expand_combined_late_lint_pass_methods { + ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) { + expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +#[macro_export] +macro_rules! declare_combined_late_lint_pass { + ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], [$hir:tt], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($passes: $passes,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($passes: $constructor,)* + } + } + + $v fn get_lints() -> LintArray { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$passes::get_lints());)* + lints + } + } + + impl<'tcx> LateLintPass<'tcx> for $name { + expand_combined_late_lint_pass_methods!([$($passes),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl LintPass for $name { + fn name(&self) -> &'static str { + panic!() + } + } + ) +} + +#[macro_export] +macro_rules! early_lint_methods { + ($macro:path, $args:tt) => ( + $macro!($args, [ + fn check_param(a: &ast::Param); + fn check_ident(a: Ident); + fn check_crate(a: &ast::Crate); + fn check_crate_post(a: &ast::Crate); + fn check_mod(a: &ast::Mod, b: Span, c: ast::NodeId); + fn check_mod_post(a: &ast::Mod, b: Span, c: ast::NodeId); + fn check_foreign_item(a: &ast::ForeignItem); + fn check_foreign_item_post(a: &ast::ForeignItem); + fn check_item(a: &ast::Item); + fn check_item_post(a: &ast::Item); + fn check_local(a: &ast::Local); + fn check_block(a: &ast::Block); + fn check_block_post(a: &ast::Block); + fn check_stmt(a: &ast::Stmt); + fn check_arm(a: &ast::Arm); + fn check_pat(a: &ast::Pat); + fn check_anon_const(a: &ast::AnonConst); + fn check_pat_post(a: &ast::Pat); + fn check_expr(a: &ast::Expr); + fn check_expr_post(a: &ast::Expr); + fn check_ty(a: &ast::Ty); + fn check_generic_param(a: &ast::GenericParam); + fn check_generics(a: &ast::Generics); + fn check_where_predicate(a: &ast::WherePredicate); + fn check_poly_trait_ref(a: &ast::PolyTraitRef, + b: &ast::TraitBoundModifier); + fn check_fn(a: rustc_ast::visit::FnKind<'_>, c: Span, d_: ast::NodeId); + fn check_fn_post( + a: rustc_ast::visit::FnKind<'_>, + c: Span, + d: ast::NodeId + ); + fn check_trait_item(a: &ast::AssocItem); + fn check_trait_item_post(a: &ast::AssocItem); + fn check_impl_item(a: &ast::AssocItem); + fn check_impl_item_post(a: &ast::AssocItem); + fn check_struct_def(a: &ast::VariantData); + fn check_struct_def_post(a: &ast::VariantData); + fn check_struct_field(a: &ast::StructField); + fn check_variant(a: &ast::Variant); + fn check_variant_post(a: &ast::Variant); + fn check_lifetime(a: &ast::Lifetime); + fn check_path(a: &ast::Path, b: ast::NodeId); + fn check_attribute(a: &ast::Attribute); + fn check_mac_def(a: &ast::MacroDef, b: ast::NodeId); + fn check_mac(a: &ast::MacCall); + + /// Called when entering a syntax node that can have lint attributes such + /// as `#[allow(...)]`. Called with *all* the attributes of that node. + fn enter_lint_attrs(a: &[ast::Attribute]); + + /// Counterpart to `enter_lint_attrs`. + fn exit_lint_attrs(a: &[ast::Attribute]); + ]); + ) +} + +macro_rules! expand_early_lint_pass_methods { + ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* + ) +} + +macro_rules! declare_early_lint_pass { + ([], [$($methods:tt)*]) => ( + pub trait EarlyLintPass: LintPass { + expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]); + } + ) +} + +early_lint_methods!(declare_early_lint_pass, []); + +#[macro_export] +macro_rules! expand_combined_early_lint_pass_method { + ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$passes.$name $params;)* + }) +} + +#[macro_export] +macro_rules! expand_combined_early_lint_pass_methods { + ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +#[macro_export] +macro_rules! declare_combined_early_lint_pass { + ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($passes: $passes,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($passes: $constructor,)* + } + } + + $v fn get_lints() -> LintArray { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$passes::get_lints());)* + lints + } + } + + impl EarlyLintPass for $name { + expand_combined_early_lint_pass_methods!([$($passes),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl LintPass for $name { + fn name(&self) -> &'static str { + panic!() + } + } + ) +} + +/// A lint pass boxed up as a trait object. +pub type EarlyLintPassObject = Box<dyn EarlyLintPass + sync::Send + sync::Sync + 'static>; +pub type LateLintPassObject = + Box<dyn for<'tcx> LateLintPass<'tcx> + sync::Send + sync::Sync + 'static>; diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs new file mode 100644 index 00000000000..d4aa4968f25 --- /dev/null +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -0,0 +1,41 @@ +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast::{Block, StmtKind}; +use rustc_errors::Applicability; +use rustc_span::Span; + +declare_lint! { + pub REDUNDANT_SEMICOLONS, + Warn, + "detects unnecessary trailing semicolons" +} + +declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]); + +impl EarlyLintPass for RedundantSemicolons { + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { + let mut seq = None; + for stmt in block.stmts.iter() { + match (&stmt.kind, &mut seq) { + (StmtKind::Empty, None) => seq = Some((stmt.span, false)), + (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true), + (_, seq) => maybe_lint_redundant_semis(cx, seq), + } + } + maybe_lint_redundant_semis(cx, &mut seq); + } +} + +fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) { + if let Some((span, multiple)) = seq.take() { + cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| { + let (msg, rem) = if multiple { + ("unnecessary trailing semicolons", "remove these semicolons") + } else { + ("unnecessary trailing semicolon", "remove this semicolon") + }; + lint.build(msg) + .span_suggestion(span, rem, String::new(), Applicability::MaybeIncorrect) + .emit(); + }); + } +} diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs new file mode 100644 index 00000000000..35c462c24c8 --- /dev/null +++ b/compiler/rustc_lint/src/types.rs @@ -0,0 +1,1240 @@ +#![allow(non_snake_case)] + +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::{is_range_literal, ExprKind, Node}; +use rustc_index::vec::Idx; +use rustc_middle::mir::interpret::{sign_extend, truncate}; +use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton}; +use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable}; +use rustc_span::source_map; +use rustc_span::symbol::sym; +use rustc_span::{Span, DUMMY_SP}; +use rustc_target::abi::Abi; +use rustc_target::abi::{Integer, LayoutOf, TagEncoding, VariantIdx, Variants}; +use rustc_target::spec::abi::Abi as SpecAbi; + +use std::cmp; +use tracing::debug; + +declare_lint! { + UNUSED_COMPARISONS, + Warn, + "comparisons made useless by limits of the types involved" +} + +declare_lint! { + OVERFLOWING_LITERALS, + Deny, + "literal out of range for its type" +} + +declare_lint! { + VARIANT_SIZE_DIFFERENCES, + Allow, + "detects enums with widely varying variant sizes" +} + +#[derive(Copy, Clone)] +pub struct TypeLimits { + /// Id of the last visited negated expression + negated_expr_id: Option<hir::HirId>, +} + +impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]); + +impl TypeLimits { + pub fn new() -> TypeLimits { + TypeLimits { negated_expr_id: None } + } +} + +/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint. +/// Returns `true` iff the lint was overridden. +fn lint_overflowing_range_endpoint<'tcx>( + cx: &LateContext<'tcx>, + lit: &hir::Lit, + lit_val: u128, + max: u128, + expr: &'tcx hir::Expr<'tcx>, + parent_expr: &'tcx hir::Expr<'tcx>, + ty: &str, +) -> bool { + // We only want to handle exclusive (`..`) ranges, + // which are represented as `ExprKind::Struct`. + let mut overwritten = false; + if let ExprKind::Struct(_, eps, _) = &parent_expr.kind { + if eps.len() != 2 { + return false; + } + // We can suggest using an inclusive range + // (`..=`) instead only if it is the `end` that is + // overflowing and only by 1. + if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max { + cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| { + let mut err = lint.build(&format!("range endpoint is out of range for `{}`", ty)); + if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) { + use ast::{LitIntType, LitKind}; + // We need to preserve the literal's suffix, + // as it may determine typing information. + let suffix = match lit.node { + LitKind::Int(_, LitIntType::Signed(s)) => s.name_str().to_string(), + LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str().to_string(), + LitKind::Int(_, LitIntType::Unsuffixed) => "".to_string(), + _ => bug!(), + }; + let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix); + err.span_suggestion( + parent_expr.span, + &"use an inclusive range instead", + suggestion, + Applicability::MachineApplicable, + ); + err.emit(); + overwritten = true; + } + }); + } + } + overwritten +} + +// For `isize` & `usize`, be conservative with the warnings, so that the +// warnings are consistent between 32- and 64-bit platforms. +fn int_ty_range(int_ty: ast::IntTy) -> (i128, i128) { + match int_ty { + ast::IntTy::Isize => (i64::MIN as i128, i64::MAX as i128), + ast::IntTy::I8 => (i8::MIN as i64 as i128, i8::MAX as i128), + ast::IntTy::I16 => (i16::MIN as i64 as i128, i16::MAX as i128), + ast::IntTy::I32 => (i32::MIN as i64 as i128, i32::MAX as i128), + ast::IntTy::I64 => (i64::MIN as i128, i64::MAX as i128), + ast::IntTy::I128 => (i128::MIN as i128, i128::MAX), + } +} + +fn uint_ty_range(uint_ty: ast::UintTy) -> (u128, u128) { + match uint_ty { + ast::UintTy::Usize => (u64::MIN as u128, u64::MAX as u128), + ast::UintTy::U8 => (u8::MIN as u128, u8::MAX as u128), + ast::UintTy::U16 => (u16::MIN as u128, u16::MAX as u128), + ast::UintTy::U32 => (u32::MIN as u128, u32::MAX as u128), + ast::UintTy::U64 => (u64::MIN as u128, u64::MAX as u128), + ast::UintTy::U128 => (u128::MIN, u128::MAX), + } +} + +fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> { + let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?; + let firstch = src.chars().next()?; + + if firstch == '0' { + match src.chars().nth(1) { + Some('x' | 'b') => return Some(src), + _ => return None, + } + } + + None +} + +fn report_bin_hex_error( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + ty: attr::IntType, + repr_str: String, + val: u128, + negative: bool, +) { + let size = Integer::from_attr(&cx.tcx, ty).size(); + cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| { + let (t, actually) = match ty { + attr::IntType::SignedInt(t) => { + let actually = sign_extend(val, size) as i128; + (t.name_str(), actually.to_string()) + } + attr::IntType::UnsignedInt(t) => { + let actually = truncate(val, size); + (t.name_str(), actually.to_string()) + } + }; + let mut err = lint.build(&format!("literal out of range for {}", t)); + err.note(&format!( + "the literal `{}` (decimal `{}`) does not fit into \ + the type `{}` and will become `{}{}`", + repr_str, val, t, actually, t + )); + if let Some(sugg_ty) = + get_type_suggestion(&cx.typeck_results().node_type(expr.hir_id), val, negative) + { + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + let (sans_suffix, _) = repr_str.split_at(pos); + err.span_suggestion( + expr.span, + &format!("consider using `{}` instead", sugg_ty), + format!("{}{}", sans_suffix, sugg_ty), + Applicability::MachineApplicable, + ); + } else { + err.help(&format!("consider using `{}` instead", sugg_ty)); + } + } + err.emit(); + }); +} + +// This function finds the next fitting type and generates a suggestion string. +// It searches for fitting types in the following way (`X < Y`): +// - `iX`: if literal fits in `uX` => `uX`, else => `iY` +// - `-iX` => `iY` +// - `uX` => `uY` +// +// No suggestion for: `isize`, `usize`. +fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> { + use rustc_ast::IntTy::*; + use rustc_ast::UintTy::*; + macro_rules! find_fit { + ($ty:expr, $val:expr, $negative:expr, + $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => { + { + let _neg = if negative { 1 } else { 0 }; + match $ty { + $($type => { + $(if !negative && val <= uint_ty_range($utypes).1 { + return Some($utypes.name_str()) + })* + $(if val <= int_ty_range($itypes).1 as u128 + _neg { + return Some($itypes.name_str()) + })* + None + },)+ + _ => None + } + } + } + } + match t.kind { + ty::Int(i) => find_fit!(i, val, negative, + I8 => [U8] => [I16, I32, I64, I128], + I16 => [U16] => [I32, I64, I128], + I32 => [U32] => [I64, I128], + I64 => [U64] => [I128], + I128 => [U128] => []), + ty::Uint(u) => find_fit!(u, val, negative, + U8 => [U8, U16, U32, U64, U128] => [], + U16 => [U16, U32, U64, U128] => [], + U32 => [U32, U64, U128] => [], + U64 => [U64, U128] => [], + U128 => [U128] => []), + _ => None, + } +} + +fn lint_int_literal<'tcx>( + cx: &LateContext<'tcx>, + type_limits: &TypeLimits, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, + t: ast::IntTy, + v: u128, +) { + let int_type = t.normalize(cx.sess().target.ptr_width); + let (min, max) = int_ty_range(int_type); + let max = max as u128; + let negative = type_limits.negated_expr_id == Some(e.hir_id); + + // Detect literal value out of range [min, max] inclusive + // avoiding use of -min to prevent overflow/panic + if (negative && v > max + 1) || (!negative && v > max) { + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error(cx, e, attr::IntType::SignedInt(t), repr_str, v, negative); + return; + } + + let par_id = cx.tcx.hir().get_parent_node(e.hir_id); + if let Node::Expr(par_e) = cx.tcx.hir().get(par_id) { + if let hir::ExprKind::Struct(..) = par_e.kind { + if is_range_literal(par_e) + && lint_overflowing_range_endpoint(cx, lit, v, max, e, par_e, t.name_str()) + { + // The overflowing literal lint was overridden. + return; + } + } + } + + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + lint.build(&format!("literal out of range for `{}`", t.name_str())) + .note(&format!( + "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + t.name_str(), + min, + max, + )) + .emit(); + }); + } +} + +fn lint_uint_literal<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, + t: ast::UintTy, +) { + let uint_type = t.normalize(cx.sess().target.ptr_width); + let (min, max) = uint_ty_range(uint_type); + let lit_val: u128 = match lit.node { + // _v is u8, within range by definition + ast::LitKind::Byte(_v) => return, + ast::LitKind::Int(v, _) => v, + _ => bug!(), + }; + if lit_val < min || lit_val > max { + let parent_id = cx.tcx.hir().get_parent_node(e.hir_id); + if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) { + match par_e.kind { + hir::ExprKind::Cast(..) => { + if let ty::Char = cx.typeck_results().expr_ty(par_e).kind { + cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| { + lint.build("only `u8` can be cast into `char`") + .span_suggestion( + par_e.span, + &"use a `char` literal instead", + format!("'\\u{{{:X}}}'", lit_val), + Applicability::MachineApplicable, + ) + .emit(); + }); + return; + } + } + hir::ExprKind::Struct(..) if is_range_literal(par_e) => { + let t = t.name_str(); + if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, par_e, t) { + // The overflowing literal lint was overridden. + return; + } + } + _ => {} + } + } + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error(cx, e, attr::IntType::UnsignedInt(t), repr_str, lit_val, false); + return; + } + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + lint.build(&format!("literal out of range for `{}`", t.name_str())) + .note(&format!( + "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + t.name_str(), + min, + max, + )) + .emit() + }); + } +} + +fn lint_literal<'tcx>( + cx: &LateContext<'tcx>, + type_limits: &TypeLimits, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, +) { + match cx.typeck_results().node_type(e.hir_id).kind { + ty::Int(t) => { + match lit.node { + ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => { + lint_int_literal(cx, type_limits, e, lit, t, v) + } + _ => bug!(), + }; + } + ty::Uint(t) => lint_uint_literal(cx, e, lit, t), + ty::Float(t) => { + let is_infinite = match lit.node { + ast::LitKind::Float(v, _) => match t { + ast::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), + ast::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), + }, + _ => bug!(), + }; + if is_infinite == Ok(true) { + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + lint.build(&format!("literal out of range for `{}`", t.name_str())) + .note(&format!( + "the literal `{}` does not fit into the type `{}` and will be converted to `std::{}::INFINITY`", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + t.name_str(), + t.name_str(), + )) + .emit(); + }); + } + } + _ => {} + } +} + +impl<'tcx> LateLintPass<'tcx> for TypeLimits { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) { + match e.kind { + hir::ExprKind::Unary(hir::UnOp::UnNeg, ref expr) => { + // propagate negation, if the negation itself isn't negated + if self.negated_expr_id != Some(e.hir_id) { + self.negated_expr_id = Some(expr.hir_id); + } + } + hir::ExprKind::Binary(binop, ref l, ref r) => { + if is_comparison(binop) && !check_limits(cx, binop, &l, &r) { + cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| { + lint.build("comparison is useless due to type limits").emit() + }); + } + } + hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit), + _ => {} + }; + + fn is_valid<T: cmp::PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool { + match binop.node { + hir::BinOpKind::Lt => v > min && v <= max, + hir::BinOpKind::Le => v >= min && v < max, + hir::BinOpKind::Gt => v >= min && v < max, + hir::BinOpKind::Ge => v > min && v <= max, + hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max, + _ => bug!(), + } + } + + fn rev_binop(binop: hir::BinOp) -> hir::BinOp { + source_map::respan( + binop.span, + match binop.node { + hir::BinOpKind::Lt => hir::BinOpKind::Gt, + hir::BinOpKind::Le => hir::BinOpKind::Ge, + hir::BinOpKind::Gt => hir::BinOpKind::Lt, + hir::BinOpKind::Ge => hir::BinOpKind::Le, + _ => return binop, + }, + ) + } + + fn check_limits( + cx: &LateContext<'_>, + binop: hir::BinOp, + l: &hir::Expr<'_>, + r: &hir::Expr<'_>, + ) -> bool { + let (lit, expr, swap) = match (&l.kind, &r.kind) { + (&hir::ExprKind::Lit(_), _) => (l, r, true), + (_, &hir::ExprKind::Lit(_)) => (r, l, false), + _ => return true, + }; + // Normalize the binop so that the literal is always on the RHS in + // the comparison + let norm_binop = if swap { rev_binop(binop) } else { binop }; + match cx.typeck_results().node_type(expr.hir_id).kind { + ty::Int(int_ty) => { + let (min, max) = int_ty_range(int_ty); + let lit_val: i128 = match lit.kind { + hir::ExprKind::Lit(ref li) => match li.node { + ast::LitKind::Int( + v, + ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed, + ) => v as i128, + _ => return true, + }, + _ => bug!(), + }; + is_valid(norm_binop, lit_val, min, max) + } + ty::Uint(uint_ty) => { + let (min, max): (u128, u128) = uint_ty_range(uint_ty); + let lit_val: u128 = match lit.kind { + hir::ExprKind::Lit(ref li) => match li.node { + ast::LitKind::Int(v, _) => v, + _ => return true, + }, + _ => bug!(), + }; + is_valid(norm_binop, lit_val, min, max) + } + _ => true, + } + } + + fn is_comparison(binop: hir::BinOp) -> bool { + match binop.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => true, + _ => false, + } + } + } +} + +declare_lint! { + IMPROPER_CTYPES, + Warn, + "proper use of libc types in foreign modules" +} + +declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]); + +declare_lint! { + IMPROPER_CTYPES_DEFINITIONS, + Warn, + "proper use of libc types in foreign item definitions" +} + +declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]); + +#[derive(Clone, Copy)] +crate enum CItemKind { + Declaration, + Definition, +} + +struct ImproperCTypesVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + mode: CItemKind, +} + +enum FfiResult<'tcx> { + FfiSafe, + FfiPhantom(Ty<'tcx>), + FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> }, +} + +crate fn nonnull_optimization_guaranteed<'tcx>(tcx: TyCtxt<'tcx>, def: &ty::AdtDef) -> bool { + tcx.get_attrs(def.did) + .iter() + .any(|a| tcx.sess.check_name(a, sym::rustc_nonnull_optimization_guaranteed)) +} + +/// Is type known to be non-null? +crate fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { + let tcx = cx.tcx; + match ty.kind { + ty::FnPtr(_) => true, + ty::Ref(..) => true, + ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, + ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => { + let marked_non_null = nonnull_optimization_guaranteed(tcx, &def); + + if marked_non_null { + return true; + } + + for variant in &def.variants { + if let Some(field) = variant.transparent_newtype_field(tcx) { + if ty_is_known_nonnull(cx, field.ty(tcx, substs), mode) { + return true; + } + } + } + + false + } + _ => false, + } +} + +/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. +/// If the type passed in was not scalar, returns None. +fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { + let tcx = cx.tcx; + Some(match ty.kind { + ty::Adt(field_def, field_substs) => { + let inner_field_ty = { + let first_non_zst_ty = + field_def.variants.iter().filter_map(|v| v.transparent_newtype_field(tcx)); + debug_assert_eq!( + first_non_zst_ty.clone().count(), + 1, + "Wrong number of fields for transparent type" + ); + first_non_zst_ty + .last() + .expect("No non-zst fields in transparent type.") + .ty(tcx, field_substs) + }; + return get_nullable_type(cx, inner_field_ty); + } + ty::Int(ty) => tcx.mk_mach_int(ty), + ty::Uint(ty) => tcx.mk_mach_uint(ty), + ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut), + // As these types are always non-null, the nullable equivalent of + // Option<T> of these types are their raw pointer counterparts. + ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }), + ty::FnPtr(..) => { + // There is no nullable equivalent for Rust's function pointers -- you + // must use an Option<fn(..) -> _> to represent it. + ty + } + + // We should only ever reach this case if ty_is_known_nonnull is extended + // to other types. + ref unhandled => { + debug!( + "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}", + unhandled, ty + ); + return None; + } + }) +} + +/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it +/// can, return the the type that `ty` can be safely converted to, otherwise return `None`. +/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`, +/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. +/// FIXME: This duplicates code in codegen. +crate fn repr_nullable_ptr<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + ckind: CItemKind, +) -> Option<Ty<'tcx>> { + debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); + if let ty::Adt(ty_def, substs) = ty.kind { + if ty_def.variants.len() != 2 { + return None; + } + + let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields; + let variant_fields = [get_variant_fields(0), get_variant_fields(1)]; + let fields = if variant_fields[0].is_empty() { + &variant_fields[1] + } else if variant_fields[1].is_empty() { + &variant_fields[0] + } else { + return None; + }; + + if fields.len() != 1 { + return None; + } + + let field_ty = fields[0].ty(cx.tcx, substs); + if !ty_is_known_nonnull(cx, field_ty, ckind) { + return None; + } + + // At this point, the field's type is known to be nonnull and the parent enum is Option-like. + // If the computed size for the field and the enum are different, the nonnull optimization isn't + // being applied (and we've got a problem somewhere). + let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); + if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { + bug!("improper_ctypes: Option nonnull optimization not applied?"); + } + + // Return the nullable type this Option-like enum can be safely represented with. + let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; + if let Abi::Scalar(field_ty_scalar) = field_ty_abi { + match (field_ty_scalar.valid_range.start(), field_ty_scalar.valid_range.end()) { + (0, _) => unreachable!("Non-null optimisation extended to a non-zero value."), + (1, _) => { + return Some(get_nullable_type(cx, field_ty).unwrap()); + } + (start, end) => unreachable!("Unhandled start and end range: ({}, {})", start, end), + }; + } + } + None +} + +impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { + /// Check if the type is array and emit an unsafe type lint. + fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + if let ty::Array(..) = ty.kind { + self.emit_ffi_unsafe_type_lint( + ty, + sp, + "passing raw arrays by value is not FFI-safe", + Some("consider passing a pointer to the array"), + ); + true + } else { + false + } + } + + /// Checks if the given field's type is "ffi-safe". + fn check_field_type_for_ffi( + &self, + cache: &mut FxHashSet<Ty<'tcx>>, + field: &ty::FieldDef, + substs: SubstsRef<'tcx>, + ) -> FfiResult<'tcx> { + let field_ty = field.ty(self.cx.tcx, substs); + if field_ty.has_opaque_types() { + self.check_type_for_ffi(cache, field_ty) + } else { + let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty); + self.check_type_for_ffi(cache, field_ty) + } + } + + /// Checks if the given `VariantDef`'s field types are "ffi-safe". + fn check_variant_for_ffi( + &self, + cache: &mut FxHashSet<Ty<'tcx>>, + ty: Ty<'tcx>, + def: &ty::AdtDef, + variant: &ty::VariantDef, + substs: SubstsRef<'tcx>, + ) -> FfiResult<'tcx> { + use FfiResult::*; + + if def.repr.transparent() { + // Can assume that only one field is not a ZST, so only check + // that field's type for FFI-safety. + if let Some(field) = variant.transparent_newtype_field(self.cx.tcx) { + self.check_field_type_for_ffi(cache, field, substs) + } else { + bug!("malformed transparent type"); + } + } else { + // We can't completely trust repr(C) markings; make sure the fields are + // actually safe. + let mut all_phantom = !variant.fields.is_empty(); + for field in &variant.fields { + match self.check_field_type_for_ffi(cache, &field, substs) { + FfiSafe => { + all_phantom = false; + } + FfiPhantom(..) if def.is_enum() => { + return FfiUnsafe { + ty, + reason: "this enum contains a PhantomData field".into(), + help: None, + }; + } + FfiPhantom(..) => {} + r => return r, + } + } + + if all_phantom { FfiPhantom(ty) } else { FfiSafe } + } + } + + /// Checks if the given type is "ffi-safe" (has a stable, well-defined + /// representation which can be exported to C code). + fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> { + use FfiResult::*; + + let tcx = self.cx.tcx; + + // Protect against infinite recursion, for example + // `struct S(*mut S);`. + // FIXME: A recursion limit is necessary as well, for irregular + // recursive types. + if !cache.insert(ty) { + return FfiSafe; + } + + match ty.kind { + ty::Adt(def, _) if def.is_box() && matches!(self.mode, CItemKind::Definition) => { + FfiSafe + } + + ty::Adt(def, substs) => { + if def.is_phantom_data() { + return FfiPhantom(ty); + } + match def.adt_kind() { + AdtKind::Struct | AdtKind::Union => { + let kind = if def.is_struct() { "struct" } else { "union" }; + + if !def.repr.c() && !def.repr.transparent() { + return FfiUnsafe { + ty, + reason: format!("this {} has unspecified layout", kind), + help: Some(format!( + "consider adding a `#[repr(C)]` or \ + `#[repr(transparent)]` attribute to this {}", + kind + )), + }; + } + + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did.is_local() { + return FfiUnsafe { + ty, + reason: format!("this {} is non-exhaustive", kind), + help: None, + }; + } + + if def.non_enum_variant().fields.is_empty() { + return FfiUnsafe { + ty, + reason: format!("this {} has no fields", kind), + help: Some(format!("consider adding a member to this {}", kind)), + }; + } + + self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs) + } + AdtKind::Enum => { + if def.variants.is_empty() { + // Empty enums are okay... although sort of useless. + return FfiSafe; + } + + // Check for a repr() attribute to specify the size of the + // discriminant. + if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() { + // Special-case types like `Option<extern fn()>`. + if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { + return FfiUnsafe { + ty, + reason: "enum has no representation hint".into(), + help: Some( + "consider adding a `#[repr(C)]`, \ + `#[repr(transparent)]`, or integer `#[repr(...)]` \ + attribute to this enum" + .into(), + ), + }; + } + } + + if def.is_variant_list_non_exhaustive() && !def.did.is_local() { + return FfiUnsafe { + ty, + reason: "this enum is non-exhaustive".into(), + help: None, + }; + } + + // Check the contained variants. + for variant in &def.variants { + let is_non_exhaustive = variant.is_field_list_non_exhaustive(); + if is_non_exhaustive && !variant.def_id.is_local() { + return FfiUnsafe { + ty, + reason: "this enum has non-exhaustive variants".into(), + help: None, + }; + } + + match self.check_variant_for_ffi(cache, ty, def, variant, substs) { + FfiSafe => (), + r => return r, + } + } + + FfiSafe + } + } + } + + ty::Char => FfiUnsafe { + ty, + reason: "the `char` type has no C equivalent".into(), + help: Some("consider using `u32` or `libc::wchar_t` instead".into()), + }, + + ty::Int(ast::IntTy::I128) | ty::Uint(ast::UintTy::U128) => FfiUnsafe { + ty, + reason: "128-bit integers don't currently have a known stable ABI".into(), + help: None, + }, + + // Primitive types with a stable representation. + ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, + + ty::Slice(_) => FfiUnsafe { + ty, + reason: "slices have no C equivalent".into(), + help: Some("consider using a raw pointer instead".into()), + }, + + ty::Dynamic(..) => { + FfiUnsafe { ty, reason: "trait objects have no C equivalent".into(), help: None } + } + + ty::Str => FfiUnsafe { + ty, + reason: "string slices have no C equivalent".into(), + help: Some("consider using `*const u8` and a length instead".into()), + }, + + ty::Tuple(..) => FfiUnsafe { + ty, + reason: "tuples have unspecified layout".into(), + help: Some("consider using a struct instead".into()), + }, + + ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) + if { + matches!(self.mode, CItemKind::Definition) + && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env) + } => + { + FfiSafe + } + + ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => { + self.check_type_for_ffi(cache, ty) + } + + ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty), + + ty::FnPtr(sig) => { + if self.is_internal_abi(sig.abi()) { + return FfiUnsafe { + ty, + reason: "this function pointer has Rust-specific calling convention".into(), + help: Some( + "consider using an `extern fn(...) -> ...` \ + function pointer instead" + .into(), + ), + }; + } + + let sig = tcx.erase_late_bound_regions(&sig); + if !sig.output().is_unit() { + let r = self.check_type_for_ffi(cache, sig.output()); + match r { + FfiSafe => {} + _ => { + return r; + } + } + } + for arg in sig.inputs() { + let r = self.check_type_for_ffi(cache, arg); + match r { + FfiSafe => {} + _ => { + return r; + } + } + } + FfiSafe + } + + ty::Foreign(..) => FfiSafe, + + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach this branch. + ty::Opaque(..) => { + FfiUnsafe { ty, reason: "opaque types have no C equivalent".into(), help: None } + } + + // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, + // so they are currently ignored for the purposes of this lint. + ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => { + FfiSafe + } + + ty::Param(..) + | ty::Projection(..) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::Generator(..) + | ty::GeneratorWitness(..) + | ty::Placeholder(..) + | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + } + } + + fn emit_ffi_unsafe_type_lint( + &mut self, + ty: Ty<'tcx>, + sp: Span, + note: &str, + help: Option<&str>, + ) { + let lint = match self.mode { + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + }; + + self.cx.struct_span_lint(lint, sp, |lint| { + let item_description = match self.mode { + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", + }; + let mut diag = lint.build(&format!( + "`extern` {} uses type `{}`, which is not FFI-safe", + item_description, ty + )); + diag.span_label(sp, "not FFI-safe"); + if let Some(help) = help { + diag.help(help); + } + diag.note(note); + if let ty::Adt(def, _) = ty.kind { + if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did) { + diag.span_note(sp, "the type is defined here"); + } + } + diag.emit(); + }); + } + + fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + struct ProhibitOpaqueTypes<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ty: Option<Ty<'tcx>>, + }; + + impl<'a, 'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool { + match ty.kind { + ty::Opaque(..) => { + self.ty = Some(ty); + true + } + // Consider opaque types within projections FFI-safe if they do not normalize + // to more opaque types. + ty::Projection(..) => { + let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); + + // If `ty` is a opaque type directly then `super_visit_with` won't invoke + // this function again. + if ty.has_opaque_types() { self.visit_ty(ty) } else { false } + } + _ => ty.super_visit_with(self), + } + } + } + + let mut visitor = ProhibitOpaqueTypes { cx: self.cx, ty: None }; + ty.visit_with(&mut visitor); + if let Some(ty) = visitor.ty { + self.emit_ffi_unsafe_type_lint(ty, sp, "opaque types have no C equivalent", None); + true + } else { + false + } + } + + fn check_type_for_ffi_and_report_errors( + &mut self, + sp: Span, + ty: Ty<'tcx>, + is_static: bool, + is_return_type: bool, + ) { + // We have to check for opaque types before `normalize_erasing_regions`, + // which will replace opaque types with their underlying concrete type. + if self.check_for_opaque_ty(sp, ty) { + // We've already emitted an error due to an opaque type. + return; + } + + // it is only OK to use this function because extern fns cannot have + // any generic types right now: + let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); + + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. So, first test that the top level isn't an array, and then + // recursively check the types inside. + if !is_static && self.check_for_array_ty(sp, ty) { + return; + } + + // Don't report FFI errors for unit return types. This check exists here, and not in + // `check_foreign_fn` (where it would make more sense) so that normalization has definitely + // happened. + if is_return_type && ty.is_unit() { + return; + } + + match self.check_type_for_ffi(&mut FxHashSet::default(), ty) { + FfiResult::FfiSafe => {} + FfiResult::FfiPhantom(ty) => { + self.emit_ffi_unsafe_type_lint(ty, sp, "composed only of `PhantomData`", None); + } + // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic + // argument, which after substitution, is `()`, then this branch can be hit. + FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {} + FfiResult::FfiUnsafe { ty, reason, help } => { + self.emit_ffi_unsafe_type_lint(ty, sp, &reason, help.as_deref()); + } + } + } + + fn check_foreign_fn(&mut self, id: hir::HirId, decl: &hir::FnDecl<'_>) { + let def_id = self.cx.tcx.hir().local_def_id(id); + let sig = self.cx.tcx.fn_sig(def_id); + let sig = self.cx.tcx.erase_late_bound_regions(&sig); + + for (input_ty, input_hir) in sig.inputs().iter().zip(decl.inputs) { + self.check_type_for_ffi_and_report_errors(input_hir.span, input_ty, false, false); + } + + if let hir::FnRetTy::Return(ref ret_hir) = decl.output { + let ret_ty = sig.output(); + self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true); + } + } + + fn check_foreign_static(&mut self, id: hir::HirId, span: Span) { + let def_id = self.cx.tcx.hir().local_def_id(id); + let ty = self.cx.tcx.type_of(def_id); + self.check_type_for_ffi_and_report_errors(span, ty, true, false); + } + + fn is_internal_abi(&self, abi: SpecAbi) -> bool { + if let SpecAbi::Rust + | SpecAbi::RustCall + | SpecAbi::RustIntrinsic + | SpecAbi::PlatformIntrinsic = abi + { + true + } else { + false + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; + let abi = cx.tcx.hir().get_foreign_abi(it.hir_id); + + if !vis.is_internal_abi(abi) { + match it.kind { + hir::ForeignItemKind::Fn(ref decl, _, _) => { + vis.check_foreign_fn(it.hir_id, decl); + } + hir::ForeignItemKind::Static(ref ty, _) => { + vis.check_foreign_static(it.hir_id, ty.span); + } + hir::ForeignItemKind::Type => (), + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: hir::intravisit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + _: &'tcx hir::Body<'_>, + _: Span, + hir_id: hir::HirId, + ) { + use hir::intravisit::FnKind; + + let abi = match kind { + FnKind::ItemFn(_, _, header, ..) => header.abi, + FnKind::Method(_, sig, ..) => sig.header.abi, + _ => return, + }; + + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + if !vis.is_internal_abi(abi) { + vis.check_foreign_fn(hir_id, decl); + } + } +} + +declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); + +impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { + let item_def_id = cx.tcx.hir().local_def_id(it.hir_id); + let t = cx.tcx.type_of(item_def_id); + let ty = cx.tcx.erase_regions(&t); + let layout = match cx.layout_of(ty) { + Ok(layout) => layout, + Err( + ty::layout::LayoutError::Unknown(_) | ty::layout::LayoutError::SizeOverflow(_), + ) => return, + }; + let (variants, tag) = match layout.variants { + Variants::Multiple { + tag_encoding: TagEncoding::Direct, + ref tag, + ref variants, + .. + } => (variants, tag), + _ => return, + }; + + let tag_size = tag.value.size(&cx.tcx).bytes(); + + debug!( + "enum `{}` is {} bytes large with layout:\n{:#?}", + t, + layout.size.bytes(), + layout + ); + + let (largest, slargest, largest_index) = enum_definition + .variants + .iter() + .zip(variants) + .map(|(variant, variant_layout)| { + // Subtract the size of the enum tag. + let bytes = variant_layout.size.bytes().saturating_sub(tag_size); + + debug!("- variant `{}` is {} bytes large", variant.ident, bytes); + bytes + }) + .enumerate() + .fold((0, 0, 0), |(l, s, li), (idx, size)| { + if size > l { + (size, l, idx) + } else if size > s { + (l, size, li) + } else { + (l, s, li) + } + }); + + // We only warn if the largest variant is at least thrice as large as + // the second-largest. + if largest > slargest * 3 && slargest > 0 { + cx.struct_span_lint( + VARIANT_SIZE_DIFFERENCES, + enum_definition.variants[largest_index].span, + |lint| { + lint.build(&format!( + "enum variant is more than three times \ + larger ({} bytes) than the next largest", + largest + )) + .emit() + }, + ); + } + } + } +} diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs new file mode 100644 index 00000000000..c793e81ebe3 --- /dev/null +++ b/compiler/rustc_lint/src/unused.rs @@ -0,0 +1,1011 @@ +use crate::Lint; +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_ast::util::parser; +use rustc_ast::{ExprKind, StmtKind}; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::{pluralize, Applicability}; +use rustc_feature::{AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_middle::ty::adjustment; +use rustc_middle::ty::{self, Ty}; +use rustc_session::lint::builtin::UNUSED_ATTRIBUTES; +use rustc_span::symbol::Symbol; +use rustc_span::symbol::{kw, sym}; +use rustc_span::{BytePos, Span, DUMMY_SP}; + +use tracing::debug; + +declare_lint! { + pub UNUSED_MUST_USE, + Warn, + "unused result of a type flagged as `#[must_use]`", + report_in_external_macro +} + +declare_lint! { + pub UNUSED_RESULTS, + Allow, + "unused result of an expression in a statement" +} + +declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); + +impl<'tcx> LateLintPass<'tcx> for UnusedResults { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + let expr = match s.kind { + hir::StmtKind::Semi(ref expr) => &**expr, + _ => return, + }; + + if let hir::ExprKind::Ret(..) = expr.kind { + return; + } + + let ty = cx.typeck_results().expr_ty(&expr); + let type_permits_lack_of_use = check_must_use_ty(cx, ty, &expr, s.span, "", "", 1); + + let mut fn_warned = false; + let mut op_warned = false; + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(ref callee, _) => { + match callee.kind { + hir::ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, callee.hir_id) { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting + _ => None, + } + } + _ => None, + } + } + hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + if let Some(def_id) = maybe_def_id { + fn_warned = check_must_use_def(cx, def_id, s.span, "return value of ", ""); + } else if type_permits_lack_of_use { + // We don't warn about unused unit or uninhabited types. + // (See https://github.com/rust-lang/rust/issues/43806 for details.) + return; + } + + let must_use_op = match expr.kind { + // Hardcoding operators here seemed more expedient than the + // refactoring that would be needed to look up the `#[must_use]` + // attribute which does exist on the comparison trait methods + hir::ExprKind::Binary(bin_op, ..) => match bin_op.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => Some("comparison"), + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Div + | hir::BinOpKind::Mul + | hir::BinOpKind::Rem => Some("arithmetic operation"), + hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"), + hir::BinOpKind::BitXor + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr + | hir::BinOpKind::Shl + | hir::BinOpKind::Shr => Some("bitwise operation"), + }, + hir::ExprKind::Unary(..) => Some("unary operation"), + _ => None, + }; + + if let Some(must_use_op) = must_use_op { + cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| { + lint.build(&format!("unused {} that must be used", must_use_op)).emit() + }); + op_warned = true; + } + + if !(type_permits_lack_of_use || fn_warned || op_warned) { + cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| lint.build("unused result").emit()); + } + + // Returns whether an error has been emitted (and thus another does not need to be later). + fn check_must_use_ty<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + ) -> bool { + if ty.is_unit() + || cx.tcx.is_ty_uninhabited_from( + cx.tcx.parent_module(expr.hir_id).to_def_id(), + ty, + cx.param_env, + ) + { + return true; + } + + let plural_suffix = pluralize!(plural_len); + + match ty.kind { + ty::Adt(..) if ty.is_box() => { + let boxed_ty = ty.boxed_ty(); + let descr_pre = &format!("{}boxed ", descr_pre); + check_must_use_ty(cx, boxed_ty, expr, span, descr_pre, descr_post, plural_len) + } + ty::Adt(def, _) => check_must_use_def(cx, def.did, span, descr_pre, descr_post), + ty::Opaque(def, _) => { + let mut has_emitted = false; + for (predicate, _) in cx.tcx.predicates_of(def).predicates { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::PredicateAtom::Trait(ref poly_trait_predicate, _) = + predicate.skip_binders() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + let descr_pre = + &format!("{}implementer{} of ", descr_pre, plural_suffix,); + if check_must_use_def(cx, def_id, span, descr_pre, descr_post) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Dynamic(binder, _) => { + let mut has_emitted = false; + for predicate in binder.skip_binder().iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate { + let def_id = trait_ref.def_id; + let descr_post = + &format!(" trait object{}{}", plural_suffix, descr_post,); + if check_must_use_def(cx, def_id, span, descr_pre, descr_post) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Tuple(ref tys) => { + let mut has_emitted = false; + let spans = if let hir::ExprKind::Tup(comps) = &expr.kind { + debug_assert_eq!(comps.len(), tys.len()); + comps.iter().map(|e| e.span).collect() + } else { + vec![] + }; + for (i, ty) in tys.iter().map(|k| k.expect_ty()).enumerate() { + let descr_post = &format!(" in tuple element {}", i); + let span = *spans.get(i).unwrap_or(&span); + if check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, plural_len) + { + has_emitted = true; + } + } + has_emitted + } + ty::Array(ty, len) => match len.try_eval_usize(cx.tcx, cx.param_env) { + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(n) if n != 0 => { + let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix,); + check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, n as usize + 1) + } + // Otherwise, we don't lint, to avoid false positives. + _ => false, + }, + ty::Closure(..) => { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + let mut err = lint.build(&format!( + "unused {}closure{}{} that must be used", + descr_pre, plural_suffix, descr_post, + )); + err.note("closures are lazy and do nothing unless called"); + err.emit(); + }); + true + } + ty::Generator(..) => { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + let mut err = lint.build(&format!( + "unused {}generator{}{} that must be used", + descr_pre, plural_suffix, descr_post, + )); + err.note("generators are lazy and do nothing unless resumed"); + err.emit(); + }); + true + } + _ => false, + } + } + + // Returns whether an error has been emitted (and thus another does not need to be later). + // FIXME: Args desc_{pre,post}_path could be made lazy by taking Fn() -> &str, but this + // would make calling it a big awkward. Could also take String (so args are moved), but + // this would still require a copy into the format string, which would only be executed + // when needed. + fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + ) -> bool { + for attr in cx.tcx.get_attrs(def_id).iter() { + if cx.sess().check_name(attr, sym::must_use) { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + let msg = format!( + "unused {}`{}`{} that must be used", + descr_pre_path, + cx.tcx.def_path_str(def_id), + descr_post_path + ); + let mut err = lint.build(&msg); + // check for #[must_use = "..."] + if let Some(note) = attr.value_str() { + err.note(¬e.as_str()); + } + err.emit(); + }); + return true; + } + } + false + } + } +} + +declare_lint! { + pub PATH_STATEMENTS, + Warn, + "path statements with no effect" +} + +declare_lint_pass!(PathStatements => [PATH_STATEMENTS]); + +impl<'tcx> LateLintPass<'tcx> for PathStatements { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + if let hir::StmtKind::Semi(expr) = s.kind { + if let hir::ExprKind::Path(_) = expr.kind { + cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| { + let ty = cx.typeck_results().expr_ty(expr); + if ty.needs_drop(cx.tcx, cx.param_env) { + let mut lint = lint.build("path statement drops value"); + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) { + lint.span_suggestion( + s.span, + "use `drop` to clarify the intent", + format!("drop({});", snippet), + Applicability::MachineApplicable, + ); + } else { + lint.span_help(s.span, "use `drop` to clarify the intent"); + } + lint.emit() + } else { + lint.build("path statement with no effect").emit() + } + }); + } + } + } +} + +#[derive(Copy, Clone)] +pub struct UnusedAttributes { + builtin_attributes: &'static FxHashMap<Symbol, &'static BuiltinAttribute>, +} + +impl UnusedAttributes { + pub fn new() -> Self { + UnusedAttributes { builtin_attributes: &*BUILTIN_ATTRIBUTE_MAP } + } +} + +impl_lint_pass!(UnusedAttributes => [UNUSED_ATTRIBUTES]); + +impl<'tcx> LateLintPass<'tcx> for UnusedAttributes { + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { + debug!("checking attribute: {:?}", attr); + + if attr.is_doc_comment() { + return; + } + + let attr_info = attr.ident().and_then(|ident| self.builtin_attributes.get(&ident.name)); + + if let Some(&&(name, ty, ..)) = attr_info { + if let AttributeType::AssumedUsed = ty { + debug!("{:?} is AssumedUsed", name); + return; + } + } + + if !cx.sess().is_attr_used(attr) { + debug!("emitting warning for: {:?}", attr); + cx.struct_span_lint(UNUSED_ATTRIBUTES, attr.span, |lint| { + lint.build("unused attribute").emit() + }); + // Is it a builtin attribute that must be used at the crate level? + if attr_info.map_or(false, |(_, ty, ..)| ty == &AttributeType::CrateLevel) { + cx.struct_span_lint(UNUSED_ATTRIBUTES, attr.span, |lint| { + let msg = match attr.style { + ast::AttrStyle::Outer => { + "crate-level attribute should be an inner attribute: add an exclamation \ + mark: `#![foo]`" + } + ast::AttrStyle::Inner => "crate-level attribute should be in the root module", + }; + lint.build(msg).emit() + }); + } + } else { + debug!("Attr was used: {:?}", attr); + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum UnusedDelimsCtx { + FunctionArg, + MethodArg, + AssignedValue, + IfCond, + WhileCond, + ForIterExpr, + MatchScrutineeExpr, + ReturnValue, + BlockRetValue, + LetScrutineeExpr, + ArrayLenExpr, + AnonConst, +} + +impl From<UnusedDelimsCtx> for &'static str { + fn from(ctx: UnusedDelimsCtx) -> &'static str { + match ctx { + UnusedDelimsCtx::FunctionArg => "function argument", + UnusedDelimsCtx::MethodArg => "method argument", + UnusedDelimsCtx::AssignedValue => "assigned value", + UnusedDelimsCtx::IfCond => "`if` condition", + UnusedDelimsCtx::WhileCond => "`while` condition", + UnusedDelimsCtx::ForIterExpr => "`for` iterator expression", + UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression", + UnusedDelimsCtx::ReturnValue => "`return` value", + UnusedDelimsCtx::BlockRetValue => "block return value", + UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression", + UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression", + } + } +} + +/// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication. +trait UnusedDelimLint { + const DELIM_STR: &'static str; + + /// Due to `ref` pattern, there can be a difference between using + /// `{ expr }` and `expr` in pattern-matching contexts. This means + /// that we should only lint `unused_parens` and not `unused_braces` + /// in this case. + /// + /// ```rust + /// let mut a = 7; + /// let ref b = { a }; // We actually borrow a copy of `a` here. + /// a += 1; // By mutating `a` we invalidate any borrows of `a`. + /// assert_eq!(b + 1, a); // `b` does not borrow `a`, so we can still use it here. + /// ``` + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool; + + // this cannot be a constant is it refers to a static. + fn lint(&self) -> &'static Lint; + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ); + + fn is_expr_delims_necessary(inner: &ast::Expr, followed_by_block: bool) -> bool { + // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }` + let lhs_needs_parens = { + let mut innermost = inner; + loop { + if let ExprKind::Binary(_, lhs, _rhs) = &innermost.kind { + innermost = lhs; + if !rustc_ast::util::classify::expr_requires_semi_to_be_stmt(innermost) { + break true; + } + } else { + break false; + } + } + }; + + lhs_needs_parens + || (followed_by_block + && match inner.kind { + ExprKind::Ret(_) | ExprKind::Break(..) | ExprKind::Yield(..) => true, + _ => parser::contains_exterior_struct_lit(&inner), + }) + } + + fn emit_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + let expr_text = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(value.span) { + snippet + } else { + pprust::expr_to_string(value) + }; + let keep_space = ( + left_pos.map(|s| s >= value.span.lo()).unwrap_or(false), + right_pos.map(|s| s <= value.span.hi()).unwrap_or(false), + ); + self.emit_unused_delims(cx, value.span, &expr_text, ctx.into(), keep_space); + } + + fn emit_unused_delims( + &self, + cx: &EarlyContext<'_>, + span: Span, + pattern: &str, + msg: &str, + keep_space: (bool, bool), + ) { + // FIXME(flip1995): Quick and dirty fix for #70814. This should be fixed in rustdoc + // properly. + if span == DUMMY_SP { + return; + } + + cx.struct_span_lint(self.lint(), span, |lint| { + let span_msg = format!("unnecessary {} around {}", Self::DELIM_STR, msg); + let mut err = lint.build(&span_msg); + let mut ate_left_paren = false; + let mut ate_right_paren = false; + let parens_removed = pattern + .trim_matches(|c| match c { + '(' | '{' => { + if ate_left_paren { + false + } else { + ate_left_paren = true; + true + } + } + ')' | '}' => { + if ate_right_paren { + false + } else { + ate_right_paren = true; + true + } + } + _ => false, + }) + .trim(); + + let replace = { + let mut replace = if keep_space.0 { + let mut s = String::from(" "); + s.push_str(parens_removed); + s + } else { + String::from(parens_removed) + }; + + if keep_space.1 { + replace.push(' '); + } + replace + }; + + let suggestion = format!("remove these {}", Self::DELIM_STR); + + err.span_suggestion_short(span, &suggestion, replace, Applicability::MachineApplicable); + err.emit(); + }); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + use rustc_ast::ExprKind::*; + let (value, ctx, followed_by_block, left_pos, right_pos) = match e.kind { + // Do not lint `unused_braces` in `if let` expressions. + If(ref cond, ref block, ..) + if !matches!(cond.kind, Let(_, _)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => + { + let left = e.span.lo() + rustc_span::BytePos(2); + let right = block.span.lo(); + (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right)) + } + + // Do not lint `unused_braces` in `while let` expressions. + While(ref cond, ref block, ..) + if !matches!(cond.kind, Let(_, _)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => + { + let left = e.span.lo() + rustc_span::BytePos(5); + let right = block.span.lo(); + (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right)) + } + + ForLoop(_, ref cond, ref block, ..) => { + (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo())) + } + + Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { + let left = e.span.lo() + rustc_span::BytePos(5); + (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None) + } + + Ret(Some(ref value)) => { + let left = e.span.lo() + rustc_span::BytePos(3); + (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None) + } + + Assign(_, ref value, _) | AssignOp(.., ref value) => { + (value, UnusedDelimsCtx::AssignedValue, false, None, None) + } + // either function/method call, or something this lint doesn't care about + ref call_or_other => { + let (args_to_check, ctx) = match *call_or_other { + Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg), + // first "argument" is self (which sometimes needs delims) + MethodCall(_, ref args, _) => (&args[1..], UnusedDelimsCtx::MethodArg), + // actual catch-all arm + _ => { + return; + } + }; + // Don't lint if this is a nested macro expansion: otherwise, the lint could + // trigger in situations that macro authors shouldn't have to care about, e.g., + // when a parenthesized token tree matched in one macro expansion is matched as + // an expression in another and used as a fn/method argument (Issue #47775) + if e.span.ctxt().outer_expn_data().call_site.from_expansion() { + return; + } + for arg in args_to_check { + self.check_unused_delims_expr(cx, arg, ctx, false, None, None); + } + return; + } + }; + self.check_unused_delims_expr(cx, &value, ctx, followed_by_block, left_pos, right_pos); + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + match s.kind { + StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { + if let Some(ref value) = local.init { + self.check_unused_delims_expr( + cx, + &value, + UnusedDelimsCtx::AssignedValue, + false, + None, + None, + ); + } + } + StmtKind::Expr(ref expr) => { + self.check_unused_delims_expr( + cx, + &expr, + UnusedDelimsCtx::BlockRetValue, + false, + None, + None, + ); + } + _ => {} + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + use ast::ItemKind::*; + + if let Const(.., Some(expr)) | Static(.., Some(expr)) = &item.kind { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::AssignedValue, + false, + None, + None, + ); + } + } +} + +declare_lint! { + pub(super) UNUSED_PARENS, + Warn, + "`if`, `match`, `while` and `return` do not need parentheses" +} + +declare_lint_pass!(UnusedParens => [UNUSED_PARENS]); + +impl UnusedDelimLint for UnusedParens { + const DELIM_STR: &'static str = "parentheses"; + + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true; + + fn lint(&self) -> &'static Lint { + UNUSED_PARENS + } + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + match value.kind { + ast::ExprKind::Paren(ref inner) => { + if !Self::is_expr_delims_necessary(inner, followed_by_block) + && value.attrs.is_empty() + && !value.span.from_expansion() + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + } + } + ast::ExprKind::Let(_, ref expr) => { + // FIXME(#60336): Properly handle `let true = (false && true)` + // actually needing the parenthesis. + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + ); + } + _ => {} + } + } +} + +impl UnusedParens { + fn check_unused_parens_pat( + &self, + cx: &EarlyContext<'_>, + value: &ast::Pat, + avoid_or: bool, + avoid_mut: bool, + ) { + use ast::{BindingMode, Mutability, PatKind}; + + if let PatKind::Paren(inner) = &value.kind { + match inner.kind { + // The lint visitor will visit each subpattern of `p`. We do not want to lint + // any range pattern no matter where it occurs in the pattern. For something like + // `&(a..=b)`, there is a recursive `check_pat` on `a` and `b`, but we will assume + // that if there are unnecessary parens they serve a purpose of readability. + PatKind::Range(..) => return, + // Avoid `p0 | .. | pn` if we should. + PatKind::Or(..) if avoid_or => return, + // Avoid `mut x` and `mut x @ p` if we should: + PatKind::Ident(BindingMode::ByValue(Mutability::Mut), ..) if avoid_mut => return, + // Otherwise proceed with linting. + _ => {} + } + + let pattern_text = + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(value.span) { + snippet + } else { + pprust::pat_to_string(value) + }; + self.emit_unused_delims(cx, value.span, &pattern_text, "pattern", (false, false)); + } + } +} + +impl EarlyLintPass for UnusedParens { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ExprKind::Let(ref pat, ..) | ExprKind::ForLoop(ref pat, ..) = e.kind { + self.check_unused_parens_pat(cx, pat, false, false); + } + + <Self as UnusedDelimLint>::check_expr(self, cx, e) + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) { + use ast::{Mutability, PatKind::*}; + match &p.kind { + // Do not lint on `(..)` as that will result in the other arms being useless. + Paren(_) + // The other cases do not contain sub-patterns. + | Wild | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) => {}, + // These are list-like patterns; parens can always be removed. + TupleStruct(_, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps { + self.check_unused_parens_pat(cx, p, false, false); + }, + Struct(_, fps, _) => for f in fps { + self.check_unused_parens_pat(cx, &f.pat, false, false); + }, + // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106. + Ident(.., Some(p)) | Box(p) => self.check_unused_parens_pat(cx, p, true, false), + // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342. + // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106. + Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not), + } + } + + fn check_anon_const(&mut self, cx: &EarlyContext<'_>, c: &ast::AnonConst) { + self.check_unused_delims_expr(cx, &c.value, UnusedDelimsCtx::AnonConst, false, None, None); + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + if let StmtKind::Local(ref local) = s.kind { + self.check_unused_parens_pat(cx, &local.pat, false, false); + } + + <Self as UnusedDelimLint>::check_stmt(self, cx, s) + } + + fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) { + self.check_unused_parens_pat(cx, ¶m.pat, true, false); + } + + fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { + self.check_unused_parens_pat(cx, &arm.pat, false, false); + } + + fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { + if let &ast::TyKind::Paren(ref r) = &ty.kind { + match &r.kind { + &ast::TyKind::TraitObject(..) => {} + &ast::TyKind::ImplTrait(_, ref bounds) if bounds.len() > 1 => {} + &ast::TyKind::Array(_, ref len) => { + self.check_unused_delims_expr( + cx, + &len.value, + UnusedDelimsCtx::ArrayLenExpr, + false, + None, + None, + ); + } + _ => { + let pattern_text = + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(ty.span) { + snippet + } else { + pprust::ty_to_string(ty) + }; + + self.emit_unused_delims(cx, ty.span, &pattern_text, "type", (false, false)); + } + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + <Self as UnusedDelimLint>::check_item(self, cx, item) + } +} + +declare_lint! { + pub(super) UNUSED_BRACES, + Warn, + "unnecessary braces around an expression" +} + +declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]); + +impl UnusedDelimLint for UnusedBraces { + const DELIM_STR: &'static str = "braces"; + + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false; + + fn lint(&self) -> &'static Lint { + UNUSED_BRACES + } + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + match value.kind { + ast::ExprKind::Block(ref inner, None) + if inner.rules == ast::BlockCheckMode::Default => + { + // emit a warning under the following conditions: + // + // - the block does not have a label + // - the block is not `unsafe` + // - the block contains exactly one expression (do not lint `{ expr; }`) + // - `followed_by_block` is true and the internal expr may contain a `{` + // - the block is not multiline (do not lint multiline match arms) + // ``` + // match expr { + // Pattern => { + // somewhat_long_expression + // } + // // ... + // } + // ``` + // - the block has no attribute and was not created inside a macro + // - if the block is an `anon_const`, the inner expr must be a literal + // (do not lint `struct A<const N: usize>; let _: A<{ 2 + 3 }>;`) + // + // FIXME(const_generics): handle paths when #67075 is fixed. + if let [stmt] = inner.stmts.as_slice() { + if let ast::StmtKind::Expr(ref expr) = stmt.kind { + if !Self::is_expr_delims_necessary(expr, followed_by_block) + && (ctx != UnusedDelimsCtx::AnonConst + || matches!(expr.kind, ast::ExprKind::Lit(_))) + // array length expressions are checked during `check_anon_const` and `check_ty`, + // once as `ArrayLenExpr` and once as `AnonConst`. + // + // As we do not want to lint this twice, we do not emit an error for + // `ArrayLenExpr` if `AnonConst` would do the same. + && (ctx != UnusedDelimsCtx::ArrayLenExpr + || !matches!(expr.kind, ast::ExprKind::Lit(_))) + && !cx.sess().source_map().is_multiline(value.span) + && value.attrs.is_empty() + && !value.span.from_expansion() + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + } + } + } + } + ast::ExprKind::Let(_, ref expr) => { + // FIXME(#60336): Properly handle `let true = (false && true)` + // actually needing the parenthesis. + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + ); + } + _ => {} + } + } +} + +impl EarlyLintPass for UnusedBraces { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + <Self as UnusedDelimLint>::check_expr(self, cx, e) + } + + fn check_anon_const(&mut self, cx: &EarlyContext<'_>, c: &ast::AnonConst) { + self.check_unused_delims_expr(cx, &c.value, UnusedDelimsCtx::AnonConst, false, None, None); + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + <Self as UnusedDelimLint>::check_stmt(self, cx, s) + } + + fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { + if let &ast::TyKind::Paren(ref r) = &ty.kind { + if let ast::TyKind::Array(_, ref len) = r.kind { + self.check_unused_delims_expr( + cx, + &len.value, + UnusedDelimsCtx::ArrayLenExpr, + false, + None, + None, + ); + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + <Self as UnusedDelimLint>::check_item(self, cx, item) + } +} + +declare_lint! { + UNUSED_IMPORT_BRACES, + Allow, + "unnecessary braces around an imported item" +} + +declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]); + +impl UnusedImportBraces { + fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) { + if let ast::UseTreeKind::Nested(ref items) = use_tree.kind { + // Recursively check nested UseTrees + for &(ref tree, _) in items { + self.check_use_tree(cx, tree, item); + } + + // Trigger the lint only if there is one nested item + if items.len() != 1 { + return; + } + + // Trigger the lint if the nested item is a non-self single item + let node_name = match items[0].0.kind { + ast::UseTreeKind::Simple(rename, ..) => { + let orig_ident = items[0].0.prefix.segments.last().unwrap().ident; + if orig_ident.name == kw::SelfLower { + return; + } + rename.unwrap_or(orig_ident).name + } + ast::UseTreeKind::Glob => Symbol::intern("*"), + ast::UseTreeKind::Nested(_) => return, + }; + + cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| { + lint.build(&format!("braces around {} is unnecessary", node_name)).emit() + }); + } + } +} + +impl EarlyLintPass for UnusedImportBraces { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if let ast::ItemKind::Use(ref use_tree) = item.kind { + self.check_use_tree(cx, use_tree, item); + } + } +} + +declare_lint! { + pub(super) UNUSED_ALLOCATION, + Warn, + "detects unnecessary allocations that can be eliminated" +} + +declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]); + +impl<'tcx> LateLintPass<'tcx> for UnusedAllocation { + fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { + match e.kind { + hir::ExprKind::Box(_) => {} + _ => return, + } + + for adj in cx.typeck_results().expr_adjustments(e) { + if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind { + cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| { + let msg = match m { + adjustment::AutoBorrowMutability::Not => { + "unnecessary allocation, use `&` instead" + } + adjustment::AutoBorrowMutability::Mut { .. } => { + "unnecessary allocation, use `&mut` instead" + } + }; + lint.build(msg).emit() + }); + } + } + } +} |
