mv compiler to compiler/
This commit is contained in:
parent
db534b3ac2
commit
9e5f7d5631
1686 changed files with 941 additions and 1051 deletions
97
compiler/rustc_lint/src/array_into_iter.rs
Normal file
97
compiler/rustc_lint/src/array_into_iter.rs
Normal 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();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
2422
compiler/rustc_lint/src/builtin.rs
Normal file
2422
compiler/rustc_lint/src/builtin.rs
Normal file
File diff suppressed because it is too large
Load diff
862
compiler/rustc_lint/src/context.rs
Normal file
862
compiler/rustc_lint/src/context.rs
Normal 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, ¬e, sugg, Applicability::MaybeIncorrect);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedImports(message, replaces) => {
|
||||
if !replaces.is_empty() {
|
||||
db.tool_only_multipart_suggestion(
|
||||
&message,
|
||||
replaces,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
|
||||
for (span, is_imported) in spans {
|
||||
let introduced = if is_imported { "imported" } else { "defined" };
|
||||
db.span_label(
|
||||
span,
|
||||
format!("the item `{}` is already {} here", ident, introduced),
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
|
||||
stability::deprecation_suggestion(&mut db, "macro", suggestion, span)
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedDocComment(span) => {
|
||||
db.span_label(span, "rustdoc does not generate documentation for macro invocations");
|
||||
db.help("to document an item produced by a macro, \
|
||||
the macro must produce the documentation as part of its expansion");
|
||||
}
|
||||
}
|
||||
// Rewrap `db`, and pass control to the user.
|
||||
decorate(LintDiagnosticBuilder::new(db));
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to
|
||||
// set the span in their `decorate` function (preferably using set_span).
|
||||
fn lookup<S: Into<MultiSpan>>(
|
||||
&self,
|
||||
lint: &'static Lint,
|
||||
span: Option<S>,
|
||||
decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
|
||||
);
|
||||
|
||||
fn struct_span_lint<S: Into<MultiSpan>>(
|
||||
&self,
|
||||
lint: &'static Lint,
|
||||
span: S,
|
||||
decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
|
||||
) {
|
||||
self.lookup(lint, Some(span), decorate);
|
||||
}
|
||||
/// Emit a lint at the appropriate level, with no associated span.
|
||||
fn lint(&self, lint: &'static Lint, decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>)) {
|
||||
self.lookup(lint, None as Option<Span>, decorate);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EarlyContext<'a> {
|
||||
pub fn new(
|
||||
sess: &'a Session,
|
||||
lint_store: &'a LintStore,
|
||||
krate: &'a ast::Crate,
|
||||
buffered: LintBuffer,
|
||||
warn_about_weird_lints: bool,
|
||||
) -> EarlyContext<'a> {
|
||||
EarlyContext {
|
||||
sess,
|
||||
krate,
|
||||
lint_store,
|
||||
builder: LintLevelsBuilder::new(sess, warn_about_weird_lints, lint_store),
|
||||
buffered,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LintContext for LateContext<'_> {
|
||||
type PassObject = LateLintPassObject;
|
||||
|
||||
/// Gets the overall compiler `Session` object.
|
||||
fn sess(&self) -> &Session {
|
||||
&self.tcx.sess
|
||||
}
|
||||
|
||||
fn lints(&self) -> &LintStore {
|
||||
&*self.lint_store
|
||||
}
|
||||
|
||||
fn lookup<S: Into<MultiSpan>>(
|
||||
&self,
|
||||
lint: &'static Lint,
|
||||
span: Option<S>,
|
||||
decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
|
||||
) {
|
||||
let hir_id = self.last_node_with_lint_attrs;
|
||||
|
||||
match span {
|
||||
Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate),
|
||||
None => self.tcx.struct_lint_node(lint, hir_id, decorate),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LintContext for EarlyContext<'_> {
|
||||
type PassObject = EarlyLintPassObject;
|
||||
|
||||
/// Gets the overall compiler `Session` object.
|
||||
fn sess(&self) -> &Session {
|
||||
&self.sess
|
||||
}
|
||||
|
||||
fn lints(&self) -> &LintStore {
|
||||
&*self.lint_store
|
||||
}
|
||||
|
||||
fn lookup<S: Into<MultiSpan>>(
|
||||
&self,
|
||||
lint: &'static Lint,
|
||||
span: Option<S>,
|
||||
decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
|
||||
) {
|
||||
self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateContext<'tcx> {
|
||||
/// Gets the type-checking results for the current body,
|
||||
/// or `None` if outside a body.
|
||||
pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
|
||||
self.cached_typeck_results.get().or_else(|| {
|
||||
self.enclosing_body.map(|body| {
|
||||
let typeck_results = self.tcx.typeck_body(body);
|
||||
self.cached_typeck_results.set(Some(typeck_results));
|
||||
typeck_results
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the type-checking results for the current body.
|
||||
/// As this will ICE if called outside bodies, only call when working with
|
||||
/// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
|
||||
#[track_caller]
|
||||
pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
|
||||
self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body")
|
||||
}
|
||||
|
||||
/// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable.
|
||||
/// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside
|
||||
/// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing.
|
||||
pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res {
|
||||
match *qpath {
|
||||
hir::QPath::Resolved(_, ref path) => path.res,
|
||||
hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self
|
||||
.maybe_typeck_results()
|
||||
.and_then(|typeck_results| typeck_results.type_dependent_def(id))
|
||||
.map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_lint_root(&self) -> hir::HirId {
|
||||
self.last_node_with_lint_attrs
|
||||
}
|
||||
|
||||
/// Check if a `DefId`'s path matches the given absolute type path usage.
|
||||
///
|
||||
/// Anonymous scopes such as `extern` imports are matched with `kw::Invalid`;
|
||||
/// inherent `impl` blocks are matched with the name of the type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore (no context or def id available)
|
||||
/// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) {
|
||||
/// // The given `def_id` is that of an `Option` type
|
||||
/// }
|
||||
/// ```
|
||||
pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool {
|
||||
let names = self.get_def_path(def_id);
|
||||
|
||||
names.len() == path.len() && names.into_iter().zip(path.iter()).all(|(a, &b)| a == b)
|
||||
}
|
||||
|
||||
/// Gets the absolute path of `def_id` as a vector of `Symbol`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore (no context or def id available)
|
||||
/// let def_path = cx.get_def_path(def_id);
|
||||
/// if let &[sym::core, sym::option, sym::Option] = &def_path[..] {
|
||||
/// // The given `def_id` is that of an `Option` type
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> {
|
||||
pub struct AbsolutePathPrinter<'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
|
||||
type Error = !;
|
||||
|
||||
type Path = Vec<Symbol>;
|
||||
type Region = ();
|
||||
type Type = ();
|
||||
type DynExistential = ();
|
||||
type Const = ();
|
||||
|
||||
fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_dyn_existential(
|
||||
self,
|
||||
_predicates: &'tcx ty::List<ty::ExistentialPredicate<'tcx>>,
|
||||
) -> Result<Self::DynExistential, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_const(self, _ct: &'tcx ty::Const<'tcx>) -> Result<Self::Const, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> {
|
||||
Ok(vec![self.tcx.original_crate_name(cnum)])
|
||||
}
|
||||
|
||||
fn path_qualified(
|
||||
self,
|
||||
self_ty: Ty<'tcx>,
|
||||
trait_ref: Option<ty::TraitRef<'tcx>>,
|
||||
) -> Result<Self::Path, Self::Error> {
|
||||
if trait_ref.is_none() {
|
||||
if let ty::Adt(def, substs) = self_ty.kind {
|
||||
return self.print_def_path(def.did, substs);
|
||||
}
|
||||
}
|
||||
|
||||
// This shouldn't ever be needed, but just in case:
|
||||
Ok(vec![match trait_ref {
|
||||
Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)),
|
||||
None => Symbol::intern(&format!("<{}>", self_ty)),
|
||||
}])
|
||||
}
|
||||
|
||||
fn path_append_impl(
|
||||
self,
|
||||
print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
|
||||
_disambiguated_data: &DisambiguatedDefPathData,
|
||||
self_ty: Ty<'tcx>,
|
||||
trait_ref: Option<ty::TraitRef<'tcx>>,
|
||||
) -> Result<Self::Path, Self::Error> {
|
||||
let mut path = print_prefix(self)?;
|
||||
|
||||
// This shouldn't ever be needed, but just in case:
|
||||
path.push(match trait_ref {
|
||||
Some(trait_ref) => Symbol::intern(&format!(
|
||||
"<impl {} for {}>",
|
||||
trait_ref.print_only_trait_path(),
|
||||
self_ty
|
||||
)),
|
||||
None => Symbol::intern(&format!("<impl {}>", self_ty)),
|
||||
});
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn path_append(
|
||||
self,
|
||||
print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
|
||||
disambiguated_data: &DisambiguatedDefPathData,
|
||||
) -> Result<Self::Path, Self::Error> {
|
||||
let mut path = print_prefix(self)?;
|
||||
|
||||
// Skip `::{{constructor}}` on tuple/unit structs.
|
||||
if let DefPathData::Ctor = disambiguated_data.data {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
path.push(disambiguated_data.data.as_symbol());
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn path_generic_args(
|
||||
self,
|
||||
print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
|
||||
_args: &[GenericArg<'tcx>],
|
||||
) -> Result<Self::Path, Self::Error> {
|
||||
print_prefix(self)
|
||||
}
|
||||
}
|
||||
|
||||
AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LayoutOf for LateContext<'tcx> {
|
||||
type Ty = Ty<'tcx>;
|
||||
type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
|
||||
|
||||
fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout {
|
||||
self.tcx.layout_of(self.param_env.and(ty))
|
||||
}
|
||||
}
|
381
compiler/rustc_lint/src/early.rs
Normal file
381
compiler/rustc_lint/src/early.rs
Normal 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, ¶m.attrs, |cx| {
|
||||
run_early_pass!(cx, check_param, param);
|
||||
ast_visit::walk_param(cx, param);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, it: &'a ast::Item) {
|
||||
self.with_lint_attrs(it.id, &it.attrs, |cx| {
|
||||
run_early_pass!(cx, check_item, it);
|
||||
ast_visit::walk_item(cx, it);
|
||||
run_early_pass!(cx, check_item_post, it);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) {
|
||||
self.with_lint_attrs(it.id, &it.attrs, |cx| {
|
||||
run_early_pass!(cx, check_foreign_item, it);
|
||||
ast_visit::walk_foreign_item(cx, it);
|
||||
run_early_pass!(cx, check_foreign_item_post, it);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &'a ast::Pat) {
|
||||
run_early_pass!(self, check_pat, p);
|
||||
self.check_id(p.id);
|
||||
ast_visit::walk_pat(self, p);
|
||||
run_early_pass!(self, check_pat_post, p);
|
||||
}
|
||||
|
||||
fn visit_anon_const(&mut self, c: &'a ast::AnonConst) {
|
||||
run_early_pass!(self, check_anon_const, c);
|
||||
ast_visit::walk_anon_const(self, c);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'a ast::Expr) {
|
||||
self.with_lint_attrs(e.id, &e.attrs, |cx| {
|
||||
run_early_pass!(cx, check_expr, e);
|
||||
ast_visit::walk_expr(cx, e);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, s: &'a ast::Stmt) {
|
||||
run_early_pass!(self, check_stmt, s);
|
||||
self.check_id(s.id);
|
||||
ast_visit::walk_stmt(self, s);
|
||||
}
|
||||
|
||||
fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) {
|
||||
run_early_pass!(self, check_fn, fk, span, id);
|
||||
self.check_id(id);
|
||||
ast_visit::walk_fn(self, fk, span);
|
||||
run_early_pass!(self, check_fn_post, fk, span, id);
|
||||
}
|
||||
|
||||
fn visit_variant_data(&mut self, s: &'a ast::VariantData) {
|
||||
run_early_pass!(self, check_struct_def, s);
|
||||
if let Some(ctor_hir_id) = s.ctor_id() {
|
||||
self.check_id(ctor_hir_id);
|
||||
}
|
||||
ast_visit::walk_struct_def(self, s);
|
||||
run_early_pass!(self, check_struct_def_post, s);
|
||||
}
|
||||
|
||||
fn visit_struct_field(&mut self, s: &'a ast::StructField) {
|
||||
self.with_lint_attrs(s.id, &s.attrs, |cx| {
|
||||
run_early_pass!(cx, check_struct_field, s);
|
||||
ast_visit::walk_struct_field(cx, s);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_variant(&mut self, v: &'a ast::Variant) {
|
||||
self.with_lint_attrs(v.id, &v.attrs, |cx| {
|
||||
run_early_pass!(cx, check_variant, v);
|
||||
ast_visit::walk_variant(cx, v);
|
||||
run_early_pass!(cx, check_variant_post, v);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, t: &'a ast::Ty) {
|
||||
run_early_pass!(self, check_ty, t);
|
||||
self.check_id(t.id);
|
||||
ast_visit::walk_ty(self, t);
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, ident: Ident) {
|
||||
run_early_pass!(self, check_ident, ident);
|
||||
}
|
||||
|
||||
fn visit_mod(&mut self, m: &'a ast::Mod, s: Span, _a: &[ast::Attribute], n: ast::NodeId) {
|
||||
run_early_pass!(self, check_mod, m, s, n);
|
||||
self.check_id(n);
|
||||
ast_visit::walk_mod(self, m);
|
||||
run_early_pass!(self, check_mod_post, m, s, n);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'a ast::Local) {
|
||||
self.with_lint_attrs(l.id, &l.attrs, |cx| {
|
||||
run_early_pass!(cx, check_local, l);
|
||||
ast_visit::walk_local(cx, l);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_block(&mut self, b: &'a ast::Block) {
|
||||
run_early_pass!(self, check_block, b);
|
||||
self.check_id(b.id);
|
||||
ast_visit::walk_block(self, b);
|
||||
run_early_pass!(self, check_block_post, b);
|
||||
}
|
||||
|
||||
fn visit_arm(&mut self, a: &'a ast::Arm) {
|
||||
run_early_pass!(self, check_arm, a);
|
||||
ast_visit::walk_arm(self, a);
|
||||
}
|
||||
|
||||
fn visit_expr_post(&mut self, e: &'a ast::Expr) {
|
||||
run_early_pass!(self, check_expr_post, e);
|
||||
}
|
||||
|
||||
fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
|
||||
run_early_pass!(self, check_generic_param, param);
|
||||
ast_visit::walk_generic_param(self, param);
|
||||
}
|
||||
|
||||
fn visit_generics(&mut self, g: &'a ast::Generics) {
|
||||
run_early_pass!(self, check_generics, g);
|
||||
ast_visit::walk_generics(self, g);
|
||||
}
|
||||
|
||||
fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) {
|
||||
run_early_pass!(self, check_where_predicate, p);
|
||||
ast_visit::walk_where_predicate(self, p);
|
||||
}
|
||||
|
||||
fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) {
|
||||
run_early_pass!(self, check_poly_trait_ref, t, m);
|
||||
ast_visit::walk_poly_trait_ref(self, t, m);
|
||||
}
|
||||
|
||||
fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
|
||||
self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt {
|
||||
ast_visit::AssocCtxt::Trait => {
|
||||
run_early_pass!(cx, check_trait_item, item);
|
||||
ast_visit::walk_assoc_item(cx, item, ctxt);
|
||||
run_early_pass!(cx, check_trait_item_post, item);
|
||||
}
|
||||
ast_visit::AssocCtxt::Impl => {
|
||||
run_early_pass!(cx, check_impl_item, item);
|
||||
ast_visit::walk_assoc_item(cx, item, ctxt);
|
||||
run_early_pass!(cx, check_impl_item_post, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_lifetime(&mut self, lt: &'a ast::Lifetime) {
|
||||
run_early_pass!(self, check_lifetime, lt);
|
||||
self.check_id(lt.id);
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) {
|
||||
run_early_pass!(self, check_path, p, id);
|
||||
self.check_id(id);
|
||||
ast_visit::walk_path(self, p);
|
||||
}
|
||||
|
||||
fn visit_attribute(&mut self, attr: &'a ast::Attribute) {
|
||||
run_early_pass!(self, check_attribute, attr);
|
||||
}
|
||||
|
||||
fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) {
|
||||
run_early_pass!(self, check_mac_def, mac, id);
|
||||
self.check_id(id);
|
||||
}
|
||||
|
||||
fn visit_mac(&mut self, mac: &'a ast::MacCall) {
|
||||
// FIXME(#54110): So, this setup isn't really right. I think
|
||||
// that (a) the librustc_ast visitor ought to be doing this as
|
||||
// part of `walk_mac`, and (b) we should be calling
|
||||
// `visit_path`, *but* that would require a `NodeId`, and I
|
||||
// want to get #53686 fixed quickly. -nmatsakis
|
||||
ast_visit::walk_path(self, &mac.path);
|
||||
|
||||
run_early_pass!(self, check_mac, mac);
|
||||
}
|
||||
}
|
||||
|
||||
struct EarlyLintPassObjects<'a> {
|
||||
lints: &'a mut [EarlyLintPassObject],
|
||||
}
|
||||
|
||||
#[allow(rustc::lint_pass_impl_without_macro)]
|
||||
impl LintPass for EarlyLintPassObjects<'_> {
|
||||
fn name(&self) -> &'static str {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expand_early_lint_pass_impl_methods {
|
||||
([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
|
||||
$(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
|
||||
for obj in self.lints.iter_mut() {
|
||||
obj.$name(context, $($param),*);
|
||||
}
|
||||
})*
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! early_lint_pass_impl {
|
||||
([], [$($methods:tt)*]) => (
|
||||
impl EarlyLintPass for EarlyLintPassObjects<'_> {
|
||||
expand_early_lint_pass_impl_methods!([$($methods)*]);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
crate::early_lint_methods!(early_lint_pass_impl, []);
|
||||
|
||||
fn early_lint_crate<T: EarlyLintPass>(
|
||||
sess: &Session,
|
||||
lint_store: &LintStore,
|
||||
krate: &ast::Crate,
|
||||
pass: T,
|
||||
buffered: LintBuffer,
|
||||
warn_about_weird_lints: bool,
|
||||
) -> LintBuffer {
|
||||
let mut cx = EarlyContextAndPass {
|
||||
context: EarlyContext::new(sess, lint_store, krate, buffered, warn_about_weird_lints),
|
||||
pass,
|
||||
};
|
||||
|
||||
// Visit the whole crate.
|
||||
cx.with_lint_attrs(ast::CRATE_NODE_ID, &krate.attrs, |cx| {
|
||||
// since the root module isn't visited as an item (because it isn't an
|
||||
// item), warn for it here.
|
||||
run_early_pass!(cx, check_crate, krate);
|
||||
|
||||
ast_visit::walk_crate(cx, krate);
|
||||
|
||||
run_early_pass!(cx, check_crate_post, krate);
|
||||
});
|
||||
cx.context.buffered
|
||||
}
|
||||
|
||||
pub fn check_ast_crate<T: EarlyLintPass>(
|
||||
sess: &Session,
|
||||
lint_store: &LintStore,
|
||||
krate: &ast::Crate,
|
||||
pre_expansion: bool,
|
||||
lint_buffer: Option<LintBuffer>,
|
||||
builtin_lints: T,
|
||||
) {
|
||||
let passes =
|
||||
if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes };
|
||||
let mut passes: Vec<_> = passes.iter().map(|p| (p)()).collect();
|
||||
let mut buffered = lint_buffer.unwrap_or_default();
|
||||
|
||||
if !sess.opts.debugging_opts.no_interleave_lints {
|
||||
buffered =
|
||||
early_lint_crate(sess, lint_store, krate, builtin_lints, buffered, pre_expansion);
|
||||
|
||||
if !passes.is_empty() {
|
||||
buffered = early_lint_crate(
|
||||
sess,
|
||||
lint_store,
|
||||
krate,
|
||||
EarlyLintPassObjects { lints: &mut passes[..] },
|
||||
buffered,
|
||||
pre_expansion,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for pass in &mut passes {
|
||||
buffered =
|
||||
sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| {
|
||||
early_lint_crate(
|
||||
sess,
|
||||
lint_store,
|
||||
krate,
|
||||
EarlyLintPassObjects { lints: slice::from_mut(pass) },
|
||||
buffered,
|
||||
pre_expansion,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// All of the buffered lints should have been emitted at this point.
|
||||
// If not, that means that we somehow buffered a lint for a node id
|
||||
// that was not lint-checked (perhaps it doesn't exist?). This is a bug.
|
||||
//
|
||||
// Rustdoc runs everybody-loops before the early lints and removes
|
||||
// function bodies, so it's totally possible for linted
|
||||
// node ids to not exist (e.g., macros defined within functions for the
|
||||
// unused_macro lint) anymore. So we only run this check
|
||||
// when we're not in rustdoc mode. (see issue #47639)
|
||||
if !sess.opts.actually_rustdoc {
|
||||
for (_id, lints) in buffered.map {
|
||||
for early_lint in lints {
|
||||
sess.delay_span_bug(early_lint.span, "failed to process buffered lint here");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
247
compiler/rustc_lint/src/internal.rs
Normal file
247
compiler/rustc_lint/src/internal.rs
Normal 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();
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
499
compiler/rustc_lint/src/late.rs
Normal file
499
compiler/rustc_lint/src/late.rs
Normal 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, ¶m.attrs, |cx| {
|
||||
lint_callback!(cx, check_param, param);
|
||||
hir_visit::walk_param(cx, param);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
|
||||
lint_callback!(self, check_body, body);
|
||||
hir_visit::walk_body(self, body);
|
||||
lint_callback!(self, check_body_post, body);
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
|
||||
let generics = self.context.generics.take();
|
||||
self.context.generics = it.kind.generics();
|
||||
self.with_lint_attrs(it.hir_id, &it.attrs, |cx| {
|
||||
cx.with_param_env(it.hir_id, |cx| {
|
||||
lint_callback!(cx, check_item, it);
|
||||
hir_visit::walk_item(cx, it);
|
||||
lint_callback!(cx, check_item_post, it);
|
||||
});
|
||||
});
|
||||
self.context.generics = generics;
|
||||
}
|
||||
|
||||
fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
|
||||
self.with_lint_attrs(it.hir_id, &it.attrs, |cx| {
|
||||
cx.with_param_env(it.hir_id, |cx| {
|
||||
lint_callback!(cx, check_foreign_item, it);
|
||||
hir_visit::walk_foreign_item(cx, it);
|
||||
lint_callback!(cx, check_foreign_item_post, it);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
|
||||
lint_callback!(self, check_pat, p);
|
||||
hir_visit::walk_pat(self, p);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
|
||||
self.with_lint_attrs(e.hir_id, &e.attrs, |cx| {
|
||||
lint_callback!(cx, check_expr, e);
|
||||
hir_visit::walk_expr(cx, e);
|
||||
lint_callback!(cx, check_expr_post, e);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) {
|
||||
// statement attributes are actually just attributes on one of
|
||||
// - item
|
||||
// - local
|
||||
// - expression
|
||||
// so we keep track of lint levels there
|
||||
lint_callback!(self, check_stmt, s);
|
||||
hir_visit::walk_stmt(self, s);
|
||||
}
|
||||
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
fk: hir_visit::FnKind<'tcx>,
|
||||
decl: &'tcx hir::FnDecl<'tcx>,
|
||||
body_id: hir::BodyId,
|
||||
span: Span,
|
||||
id: hir::HirId,
|
||||
) {
|
||||
// Wrap in typeck results here, not just in visit_nested_body,
|
||||
// in order for `check_fn` to be able to use them.
|
||||
let old_enclosing_body = self.context.enclosing_body.replace(body_id);
|
||||
let old_cached_typeck_results = self.context.cached_typeck_results.take();
|
||||
let body = self.context.tcx.hir().body(body_id);
|
||||
lint_callback!(self, check_fn, fk, decl, body, span, id);
|
||||
hir_visit::walk_fn(self, fk, decl, body_id, span, id);
|
||||
lint_callback!(self, check_fn_post, fk, decl, body, span, id);
|
||||
self.context.enclosing_body = old_enclosing_body;
|
||||
self.context.cached_typeck_results.set(old_cached_typeck_results);
|
||||
}
|
||||
|
||||
fn visit_variant_data(
|
||||
&mut self,
|
||||
s: &'tcx hir::VariantData<'tcx>,
|
||||
_: Symbol,
|
||||
_: &'tcx hir::Generics<'tcx>,
|
||||
_: hir::HirId,
|
||||
_: Span,
|
||||
) {
|
||||
lint_callback!(self, check_struct_def, s);
|
||||
hir_visit::walk_struct_def(self, s);
|
||||
lint_callback!(self, check_struct_def_post, s);
|
||||
}
|
||||
|
||||
fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) {
|
||||
self.with_lint_attrs(s.hir_id, &s.attrs, |cx| {
|
||||
lint_callback!(cx, check_struct_field, s);
|
||||
hir_visit::walk_struct_field(cx, s);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_variant(
|
||||
&mut self,
|
||||
v: &'tcx hir::Variant<'tcx>,
|
||||
g: &'tcx hir::Generics<'tcx>,
|
||||
item_id: hir::HirId,
|
||||
) {
|
||||
self.with_lint_attrs(v.id, &v.attrs, |cx| {
|
||||
lint_callback!(cx, check_variant, v);
|
||||
hir_visit::walk_variant(cx, v, g, item_id);
|
||||
lint_callback!(cx, check_variant_post, v);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) {
|
||||
lint_callback!(self, check_ty, t);
|
||||
hir_visit::walk_ty(self, t);
|
||||
}
|
||||
|
||||
fn visit_name(&mut self, sp: Span, name: Symbol) {
|
||||
lint_callback!(self, check_name, sp, name);
|
||||
}
|
||||
|
||||
fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) {
|
||||
if !self.context.only_module {
|
||||
self.process_mod(m, s, n);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
self.with_lint_attrs(l.hir_id, &l.attrs, |cx| {
|
||||
lint_callback!(cx, check_local, l);
|
||||
hir_visit::walk_local(cx, l);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) {
|
||||
lint_callback!(self, check_block, b);
|
||||
hir_visit::walk_block(self, b);
|
||||
lint_callback!(self, check_block_post, b);
|
||||
}
|
||||
|
||||
fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
|
||||
lint_callback!(self, check_arm, a);
|
||||
hir_visit::walk_arm(self, a);
|
||||
}
|
||||
|
||||
fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
|
||||
lint_callback!(self, check_generic_param, p);
|
||||
hir_visit::walk_generic_param(self, p);
|
||||
}
|
||||
|
||||
fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) {
|
||||
lint_callback!(self, check_generics, g);
|
||||
hir_visit::walk_generics(self, g);
|
||||
}
|
||||
|
||||
fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) {
|
||||
lint_callback!(self, check_where_predicate, p);
|
||||
hir_visit::walk_where_predicate(self, p);
|
||||
}
|
||||
|
||||
fn visit_poly_trait_ref(
|
||||
&mut self,
|
||||
t: &'tcx hir::PolyTraitRef<'tcx>,
|
||||
m: hir::TraitBoundModifier,
|
||||
) {
|
||||
lint_callback!(self, check_poly_trait_ref, t, m);
|
||||
hir_visit::walk_poly_trait_ref(self, t, m);
|
||||
}
|
||||
|
||||
fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
|
||||
let generics = self.context.generics.take();
|
||||
self.context.generics = Some(&trait_item.generics);
|
||||
self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |cx| {
|
||||
cx.with_param_env(trait_item.hir_id, |cx| {
|
||||
lint_callback!(cx, check_trait_item, trait_item);
|
||||
hir_visit::walk_trait_item(cx, trait_item);
|
||||
lint_callback!(cx, check_trait_item_post, trait_item);
|
||||
});
|
||||
});
|
||||
self.context.generics = generics;
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
|
||||
let generics = self.context.generics.take();
|
||||
self.context.generics = Some(&impl_item.generics);
|
||||
self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |cx| {
|
||||
cx.with_param_env(impl_item.hir_id, |cx| {
|
||||
lint_callback!(cx, check_impl_item, impl_item);
|
||||
hir_visit::walk_impl_item(cx, impl_item);
|
||||
lint_callback!(cx, check_impl_item_post, impl_item);
|
||||
});
|
||||
});
|
||||
self.context.generics = generics;
|
||||
}
|
||||
|
||||
fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) {
|
||||
lint_callback!(self, check_lifetime, lt);
|
||||
hir_visit::walk_lifetime(self, lt);
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, p: &'tcx hir::Path<'tcx>, id: hir::HirId) {
|
||||
lint_callback!(self, check_path, p, id);
|
||||
hir_visit::walk_path(self, p);
|
||||
}
|
||||
|
||||
fn visit_attribute(&mut self, attr: &'tcx ast::Attribute) {
|
||||
lint_callback!(self, check_attribute, attr);
|
||||
}
|
||||
}
|
||||
|
||||
struct LateLintPassObjects<'a> {
|
||||
lints: &'a mut [LateLintPassObject],
|
||||
}
|
||||
|
||||
#[allow(rustc::lint_pass_impl_without_macro)]
|
||||
impl LintPass for LateLintPassObjects<'_> {
|
||||
fn name(&self) -> &'static str {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expand_late_lint_pass_impl_methods {
|
||||
([$hir:tt], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
|
||||
$(fn $name(&mut self, context: &LateContext<$hir>, $($param: $arg),*) {
|
||||
for obj in self.lints.iter_mut() {
|
||||
obj.$name(context, $($param),*);
|
||||
}
|
||||
})*
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! late_lint_pass_impl {
|
||||
([], [$hir:tt], $methods:tt) => {
|
||||
impl<$hir> LateLintPass<$hir> for LateLintPassObjects<'_> {
|
||||
expand_late_lint_pass_impl_methods!([$hir], $methods);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
crate::late_lint_methods!(late_lint_pass_impl, [], ['tcx]);
|
||||
|
||||
fn late_lint_mod_pass<'tcx, T: LateLintPass<'tcx>>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
module_def_id: LocalDefId,
|
||||
pass: T,
|
||||
) {
|
||||
let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE);
|
||||
|
||||
let context = LateContext {
|
||||
tcx,
|
||||
enclosing_body: None,
|
||||
cached_typeck_results: Cell::new(None),
|
||||
param_env: ty::ParamEnv::empty(),
|
||||
access_levels,
|
||||
lint_store: unerased_lint_store(tcx),
|
||||
last_node_with_lint_attrs: tcx.hir().local_def_id_to_hir_id(module_def_id),
|
||||
generics: None,
|
||||
only_module: true,
|
||||
};
|
||||
|
||||
let mut cx = LateContextAndPass { context, pass };
|
||||
|
||||
let (module, span, hir_id) = tcx.hir().get_module(module_def_id);
|
||||
cx.process_mod(module, span, hir_id);
|
||||
|
||||
// Visit the crate attributes
|
||||
if hir_id == hir::CRATE_HIR_ID {
|
||||
walk_list!(cx, visit_attribute, tcx.hir().attrs(hir::CRATE_HIR_ID));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx>>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
module_def_id: LocalDefId,
|
||||
builtin_lints: T,
|
||||
) {
|
||||
if tcx.sess.opts.debugging_opts.no_interleave_lints {
|
||||
// These passes runs in late_lint_crate with -Z no_interleave_lints
|
||||
return;
|
||||
}
|
||||
|
||||
late_lint_mod_pass(tcx, module_def_id, builtin_lints);
|
||||
|
||||
let mut passes: Vec<_> =
|
||||
unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
|
||||
|
||||
if !passes.is_empty() {
|
||||
late_lint_mod_pass(tcx, module_def_id, LateLintPassObjects { lints: &mut passes[..] });
|
||||
}
|
||||
}
|
||||
|
||||
fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T) {
|
||||
let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE);
|
||||
|
||||
let krate = tcx.hir().krate();
|
||||
|
||||
let context = LateContext {
|
||||
tcx,
|
||||
enclosing_body: None,
|
||||
cached_typeck_results: Cell::new(None),
|
||||
param_env: ty::ParamEnv::empty(),
|
||||
access_levels,
|
||||
lint_store: unerased_lint_store(tcx),
|
||||
last_node_with_lint_attrs: hir::CRATE_HIR_ID,
|
||||
generics: None,
|
||||
only_module: false,
|
||||
};
|
||||
|
||||
let mut cx = LateContextAndPass { context, pass };
|
||||
|
||||
// Visit the whole crate.
|
||||
cx.with_lint_attrs(hir::CRATE_HIR_ID, &krate.item.attrs, |cx| {
|
||||
// since the root module isn't visited as an item (because it isn't an
|
||||
// item), warn for it here.
|
||||
lint_callback!(cx, check_crate, krate);
|
||||
|
||||
hir_visit::walk_crate(cx, krate);
|
||||
|
||||
lint_callback!(cx, check_crate_post, krate);
|
||||
})
|
||||
}
|
||||
|
||||
fn late_lint_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, builtin_lints: T) {
|
||||
let mut passes = unerased_lint_store(tcx).late_passes.iter().map(|p| (p)()).collect::<Vec<_>>();
|
||||
|
||||
if !tcx.sess.opts.debugging_opts.no_interleave_lints {
|
||||
if !passes.is_empty() {
|
||||
late_lint_pass_crate(tcx, LateLintPassObjects { lints: &mut passes[..] });
|
||||
}
|
||||
|
||||
late_lint_pass_crate(tcx, builtin_lints);
|
||||
} else {
|
||||
for pass in &mut passes {
|
||||
tcx.sess.prof.extra_verbose_generic_activity("run_late_lint", pass.name()).run(|| {
|
||||
late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
|
||||
});
|
||||
}
|
||||
|
||||
let mut passes: Vec<_> =
|
||||
unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
|
||||
|
||||
for pass in &mut passes {
|
||||
tcx.sess.prof.extra_verbose_generic_activity("run_late_module_lint", pass.name()).run(
|
||||
|| {
|
||||
late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs lint checking on a crate.
|
||||
pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
builtin_lints: impl FnOnce() -> T + Send,
|
||||
) {
|
||||
join(
|
||||
|| {
|
||||
tcx.sess.time("crate_lints", || {
|
||||
// Run whole crate non-incremental lints
|
||||
late_lint_crate(tcx, builtin_lints());
|
||||
});
|
||||
},
|
||||
|| {
|
||||
tcx.sess.time("module_lints", || {
|
||||
// Run per-module lints
|
||||
par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
|
||||
tcx.ensure().lint_mod(tcx.hir().local_def_id(module));
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
576
compiler/rustc_lint/src/levels.rs
Normal file
576
compiler/rustc_lint/src/levels.rs
Normal 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, ¶m.attrs, |builder| {
|
||||
intravisit::walk_param(builder, param);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
|
||||
self.with_lint_attrs(it.hir_id, &it.attrs, |builder| {
|
||||
intravisit::walk_item(builder, it);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
|
||||
self.with_lint_attrs(it.hir_id, &it.attrs, |builder| {
|
||||
intravisit::walk_foreign_item(builder, it);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
|
||||
self.with_lint_attrs(e.hir_id, &e.attrs, |builder| {
|
||||
intravisit::walk_expr(builder, e);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) {
|
||||
self.with_lint_attrs(s.hir_id, &s.attrs, |builder| {
|
||||
intravisit::walk_struct_field(builder, s);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_variant(
|
||||
&mut self,
|
||||
v: &'tcx hir::Variant<'tcx>,
|
||||
g: &'tcx hir::Generics<'tcx>,
|
||||
item_id: hir::HirId,
|
||||
) {
|
||||
self.with_lint_attrs(v.id, &v.attrs, |builder| {
|
||||
intravisit::walk_variant(builder, v, g, item_id);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
self.with_lint_attrs(l.hir_id, &l.attrs, |builder| {
|
||||
intravisit::walk_local(builder, l);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
|
||||
self.with_lint_attrs(a.hir_id, &a.attrs, |builder| {
|
||||
intravisit::walk_arm(builder, a);
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
|
||||
self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |builder| {
|
||||
intravisit::walk_trait_item(builder, trait_item);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
|
||||
self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |builder| {
|
||||
intravisit::walk_impl_item(builder, impl_item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
providers.lint_levels = lint_levels;
|
||||
}
|
464
compiler/rustc_lint/src/lib.rs
Normal file
464
compiler/rustc_lint/src/lib.rs
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
240
compiler/rustc_lint/src/non_ascii_idents.rs
Normal file
240
compiler/rustc_lint/src/non_ascii_idents.rs
Normal 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(¬e).note("Please recheck to make sure their usages are indeed what you want.").emit()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
456
compiler/rustc_lint/src/nonstandard_style.rs
Normal file
456
compiler/rustc_lint/src/nonstandard_style.rs
Normal 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", ¶m.ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
pub NON_SNAKE_CASE,
|
||||
Warn,
|
||||
"variables, methods, functions, lifetime parameters and modules should have snake case names"
|
||||
}
|
||||
|
||||
declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
|
||||
|
||||
impl NonSnakeCase {
|
||||
fn to_snake_case(mut str: &str) -> String {
|
||||
let mut words = vec![];
|
||||
// Preserve leading underscores
|
||||
str = str.trim_start_matches(|c: char| {
|
||||
if c == '_' {
|
||||
words.push(String::new());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
for s in str.split('_') {
|
||||
let mut last_upper = false;
|
||||
let mut buf = String::new();
|
||||
if s.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for ch in s.chars() {
|
||||
if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
|
||||
words.push(buf);
|
||||
buf = String::new();
|
||||
}
|
||||
last_upper = ch.is_uppercase();
|
||||
buf.extend(ch.to_lowercase());
|
||||
}
|
||||
words.push(buf);
|
||||
}
|
||||
words.join("_")
|
||||
}
|
||||
|
||||
/// Checks if a given identifier is snake case, and reports a diagnostic if not.
|
||||
fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
|
||||
fn is_snake_case(ident: &str) -> bool {
|
||||
if ident.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let ident = ident.trim_start_matches('\'');
|
||||
let ident = ident.trim_matches('_');
|
||||
|
||||
let mut allow_underscore = true;
|
||||
ident.chars().all(|c| {
|
||||
allow_underscore = match c {
|
||||
'_' if !allow_underscore => return false,
|
||||
'_' => false,
|
||||
// It would be more obvious to use `c.is_lowercase()`,
|
||||
// but some characters do not have a lowercase form
|
||||
c if !c.is_uppercase() => true,
|
||||
_ => return false,
|
||||
};
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
let name = &ident.name.as_str();
|
||||
|
||||
if !is_snake_case(name) {
|
||||
cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| {
|
||||
let sc = NonSnakeCase::to_snake_case(name);
|
||||
let msg = format!("{} `{}` should have a snake case name", sort, name);
|
||||
let mut err = lint.build(&msg);
|
||||
// We have a valid span in almost all cases, but we don't have one when linting a crate
|
||||
// name provided via the command line.
|
||||
if !ident.span.is_dummy() {
|
||||
err.span_suggestion(
|
||||
ident.span,
|
||||
"convert the identifier to snake case",
|
||||
sc,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
err.help(&format!("convert the identifier to snake case: `{}`", sc));
|
||||
}
|
||||
|
||||
err.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
|
||||
fn check_mod(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
_: &'tcx hir::Mod<'tcx>,
|
||||
_: Span,
|
||||
id: hir::HirId,
|
||||
) {
|
||||
if id != hir::CRATE_HIR_ID {
|
||||
return;
|
||||
}
|
||||
|
||||
let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
|
||||
Some(Ident::from_str(name))
|
||||
} else {
|
||||
cx.sess()
|
||||
.find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name)
|
||||
.and_then(|attr| attr.meta())
|
||||
.and_then(|meta| {
|
||||
meta.name_value_literal().and_then(|lit| {
|
||||
if let ast::LitKind::Str(name, ..) = lit.kind {
|
||||
// Discard the double quotes surrounding the literal.
|
||||
let sp = cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(lit.span)
|
||||
.ok()
|
||||
.and_then(|snippet| {
|
||||
let left = snippet.find('"')?;
|
||||
let right =
|
||||
snippet.rfind('"').map(|pos| snippet.len() - pos)?;
|
||||
|
||||
Some(
|
||||
lit.span
|
||||
.with_lo(lit.span.lo() + BytePos(left as u32 + 1))
|
||||
.with_hi(lit.span.hi() - BytePos(right as u32)),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| lit.span);
|
||||
|
||||
Some(Ident::new(name, sp))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(ident) = &crate_ident {
|
||||
self.check_snake_case(cx, "crate", ident);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
|
||||
if let GenericParamKind::Lifetime { .. } = param.kind {
|
||||
self.check_snake_case(cx, "lifetime", ¶m.name.ident());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
fk: FnKind<'_>,
|
||||
_: &hir::FnDecl<'_>,
|
||||
_: &hir::Body<'_>,
|
||||
_: Span,
|
||||
id: hir::HirId,
|
||||
) {
|
||||
match &fk {
|
||||
FnKind::Method(ident, ..) => match method_context(cx, id) {
|
||||
MethodLateContext::PlainImpl => {
|
||||
self.check_snake_case(cx, "method", ident);
|
||||
}
|
||||
MethodLateContext::TraitAutoImpl => {
|
||||
self.check_snake_case(cx, "trait method", ident);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
FnKind::ItemFn(ident, _, header, _, attrs) => {
|
||||
// Skip foreign-ABI #[no_mangle] functions (Issue #31924)
|
||||
if header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) {
|
||||
return;
|
||||
}
|
||||
self.check_snake_case(cx, "function", ident);
|
||||
}
|
||||
FnKind::Closure(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
|
||||
if let hir::ItemKind::Mod(_) = it.kind {
|
||||
self.check_snake_case(cx, "module", &it.ident);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
|
||||
self.check_snake_case(cx, "trait method", &item.ident);
|
||||
for param_name in pnames {
|
||||
self.check_snake_case(cx, "variable", param_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
|
||||
if let &PatKind::Binding(_, hid, ident, _) = &p.kind {
|
||||
if let hir::Node::Pat(parent_pat) = cx.tcx.hir().get(cx.tcx.hir().get_parent_node(hid))
|
||||
{
|
||||
if let PatKind::Struct(_, field_pats, _) = &parent_pat.kind {
|
||||
for field in field_pats.iter() {
|
||||
if field.ident != ident {
|
||||
// Only check if a new name has been introduced, to avoid warning
|
||||
// on both the struct definition and this pattern.
|
||||
self.check_snake_case(cx, "variable", &ident);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.check_snake_case(cx, "variable", &ident);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
|
||||
for sf in s.fields() {
|
||||
self.check_snake_case(cx, "structure field", &sf.ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
pub NON_UPPER_CASE_GLOBALS,
|
||||
Warn,
|
||||
"static constants should have uppercase identifiers"
|
||||
}
|
||||
|
||||
declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
|
||||
|
||||
impl NonUpperCaseGlobals {
|
||||
fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
|
||||
let name = &ident.name.as_str();
|
||||
if name.chars().any(|c| c.is_lowercase()) {
|
||||
cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| {
|
||||
let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
|
||||
lint.build(&format!("{} `{}` should have an upper case name", sort, name))
|
||||
.span_suggestion(
|
||||
ident.span,
|
||||
"convert the identifier to upper case",
|
||||
uc,
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
.emit();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
|
||||
match it.kind {
|
||||
hir::ItemKind::Static(..) if !cx.sess().contains_name(&it.attrs, sym::no_mangle) => {
|
||||
NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident);
|
||||
}
|
||||
hir::ItemKind::Const(..) => {
|
||||
NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
|
||||
if let hir::TraitItemKind::Const(..) = ti.kind {
|
||||
NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
|
||||
if let hir::ImplItemKind::Const(..) = ii.kind {
|
||||
NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
|
||||
// Lint for constants that look like binding identifiers (#7526)
|
||||
if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind {
|
||||
if let Res::Def(DefKind::Const, _) = path.res {
|
||||
if path.segments.len() == 1 {
|
||||
NonUpperCaseGlobals::check_upper_case(
|
||||
cx,
|
||||
"constant in pattern",
|
||||
&path.segments[0].ident,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
|
||||
if let GenericParamKind::Const { .. } = param.kind {
|
||||
NonUpperCaseGlobals::check_upper_case(cx, "const parameter", ¶m.name.ident());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
21
compiler/rustc_lint/src/nonstandard_style/tests.rs
Normal file
21
compiler/rustc_lint/src/nonstandard_style/tests.rs
Normal 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");
|
||||
}
|
285
compiler/rustc_lint/src/passes.rs
Normal file
285
compiler/rustc_lint/src/passes.rs
Normal 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>;
|
41
compiler/rustc_lint/src/redundant_semicolon.rs
Normal file
41
compiler/rustc_lint/src/redundant_semicolon.rs
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
1240
compiler/rustc_lint/src/types.rs
Normal file
1240
compiler/rustc_lint/src/types.rs
Normal file
File diff suppressed because it is too large
Load diff
1011
compiler/rustc_lint/src/unused.rs
Normal file
1011
compiler/rustc_lint/src/unused.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue