1
Fork 0

mv compiler to compiler/

This commit is contained in:
mark 2020-08-27 22:58:48 -05:00 committed by Vadim Petrochenkov
parent db534b3ac2
commit 9e5f7d5631
1686 changed files with 941 additions and 1051 deletions

View file

@ -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();
})
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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, &note, 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))
}
}

View file

@ -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, &param.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");
}
}
}
}

View file

@ -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();
},
)
}
}
}
}
}
}

View file

@ -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, &param.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));
});
});
},
);
}

View file

@ -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, &param.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;
}

View file

@ -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),
],
);
}

View file

@ -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(&note).note("Please recheck to make sure their usages are indeed what you want.").emit()
});
}
}
}
}
}

View file

@ -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", &param.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", &param.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", &param.name.ident());
}
}
}
#[cfg(test)]
mod tests;

View file

@ -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");
}

View file

@ -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>;

View file

@ -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();
});
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff