Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
@@ -168,6 +168,14 @@ impl Constant {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peel_refs(mut self) -> Self {
|
||||
while let Constant::Ref(r) = self {
|
||||
self = *r;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `LitKind` to a `Constant`.
|
||||
@@ -320,7 +328,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
||||
let res = self.typeck_results.qpath_res(qpath, callee.hir_id);
|
||||
if let Some(def_id) = res.opt_def_id();
|
||||
let def_path = self.lcx.get_def_path(def_id);
|
||||
let def_path: Vec<&str> = def_path.iter().take(4).map(|s| s.as_str()).collect();
|
||||
let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).collect();
|
||||
if let ["core", "num", int_impl, "max_value"] = *def_path;
|
||||
then {
|
||||
let value = match int_impl {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#![feature(box_patterns)]
|
||||
#![feature(control_flow_enum)]
|
||||
#![feature(in_band_lifetimes)]
|
||||
#![feature(let_else)]
|
||||
#![feature(once_cell)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(control_flow_enum)]
|
||||
#![recursion_limit = "512"]
|
||||
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
|
||||
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
|
||||
@@ -60,9 +61,12 @@ pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, Spanl
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::lazy::SyncOnceCell;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, Attribute, LitKind};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::unhash::UnhashMap;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
@@ -87,6 +91,7 @@ use rustc_middle::ty::binding::BindingMode;
|
||||
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::source_map::original_sp;
|
||||
use rustc_span::sym;
|
||||
@@ -142,6 +147,13 @@ macro_rules! extract_msrv_attr {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns `true` if the span comes from a macro expansion, no matter if from a
|
||||
/// macro by example or from a procedural macro
|
||||
#[must_use]
|
||||
pub fn in_macro(span: Span) -> bool {
|
||||
span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the two spans come from differing expansions (i.e., one is
|
||||
/// from a macro and one isn't).
|
||||
#[must_use]
|
||||
@@ -156,18 +168,18 @@ pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
|
||||
/// instead.
|
||||
///
|
||||
/// Examples:
|
||||
/// ```ignore
|
||||
/// ```
|
||||
/// let abc = 1;
|
||||
/// // ^ output
|
||||
/// let def = abc;
|
||||
/// dbg!(def)
|
||||
/// dbg!(def);
|
||||
/// // ^^^ input
|
||||
///
|
||||
/// // or...
|
||||
/// let abc = 1;
|
||||
/// let def = abc + 2;
|
||||
/// // ^^^^^^^ output
|
||||
/// dbg!(def)
|
||||
/// dbg!(def);
|
||||
/// // ^^^ input
|
||||
/// ```
|
||||
pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
|
||||
@@ -664,6 +676,22 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
|
||||
false
|
||||
}
|
||||
|
||||
/// Return true if the expr is equal to `Default::default` when evaluated.
|
||||
pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
|
||||
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
|
||||
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|
||||
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
|
||||
then {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
|
||||
/// It doesn't cover all cases, for example indirect function calls (some of std
|
||||
/// functions are supported) but it is the best we have.
|
||||
@@ -686,18 +714,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::Call(repl_func, _) => if_chain! {
|
||||
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
|
||||
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
|
||||
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|
||||
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
|
||||
then {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::Call(repl_func, _) => is_default_equivalent_call(cx, repl_func),
|
||||
ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
|
||||
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
|
||||
_ => false,
|
||||
@@ -1136,7 +1153,7 @@ pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
|
||||
|
||||
/// Extends the span to the beginning of the spans line, incl. whitespaces.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// ```rust
|
||||
/// let x = ();
|
||||
/// // ^^
|
||||
/// // will be converted to
|
||||
@@ -1337,7 +1354,7 @@ pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
cx.typeck_results().adjustments().get(e.hir_id).is_some()
|
||||
}
|
||||
|
||||
/// Returns the pre-expansion span if is this comes from an expansion of the
|
||||
/// Returns the pre-expansion span if this comes from an expansion of the
|
||||
/// macro `name`.
|
||||
/// See also [`is_direct_expn_of`].
|
||||
#[must_use]
|
||||
@@ -1364,7 +1381,8 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
|
||||
/// of the macro `name`.
|
||||
/// The difference with [`is_expn_of`] is that in
|
||||
/// ```rust
|
||||
/// # macro_rules! foo { ($e:tt) => { $e } }; macro_rules! bar { ($e:expr) => { $e } }
|
||||
/// # macro_rules! foo { ($name:tt!$args:tt) => { $name!$args } }
|
||||
/// # macro_rules! bar { ($e:expr) => { $e } }
|
||||
/// foo!(bar!(42));
|
||||
/// ```
|
||||
/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
|
||||
@@ -1905,7 +1923,9 @@ pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
|
||||
|
||||
/// Check if parent of a hir node is a trait implementation block.
|
||||
/// For example, `f` in
|
||||
/// ```rust,ignore
|
||||
/// ```rust
|
||||
/// # struct S;
|
||||
/// # trait Trait { fn f(); }
|
||||
/// impl Trait for S {
|
||||
/// fn f() {}
|
||||
/// }
|
||||
@@ -2124,17 +2144,16 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
struct VisitConstTestStruct<'tcx> {
|
||||
struct TestItemNamesVisitor<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
names: Vec<Symbol>,
|
||||
found: bool,
|
||||
}
|
||||
impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
|
||||
|
||||
impl<'hir> ItemLikeVisitor<'hir> for TestItemNamesVisitor<'hir> {
|
||||
fn visit_item(&mut self, item: &Item<'_>) {
|
||||
if let ItemKind::Const(ty, _body) = item.kind {
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
|
||||
// We could also check for the type name `test::TestDescAndFn`
|
||||
// and the `#[rustc_test_marker]` attribute?
|
||||
if let Res::Def(DefKind::Struct, _) = path.res {
|
||||
let has_test_marker = self
|
||||
.tcx
|
||||
@@ -2142,8 +2161,8 @@ impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
|
||||
.attrs(item.hir_id())
|
||||
.iter()
|
||||
.any(|a| a.has_name(sym::rustc_test_marker));
|
||||
if has_test_marker && self.names.contains(&item.ident.name) {
|
||||
self.found = true;
|
||||
if has_test_marker {
|
||||
self.names.push(item.ident.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2154,32 +2173,42 @@ impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
|
||||
fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {}
|
||||
}
|
||||
|
||||
static TEST_ITEM_NAMES_CACHE: SyncOnceCell<Mutex<FxHashMap<LocalDefId, Vec<Symbol>>>> = SyncOnceCell::new();
|
||||
|
||||
fn with_test_item_names(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool {
|
||||
let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
|
||||
let mut map: MutexGuard<'_, FxHashMap<LocalDefId, Vec<Symbol>>> = cache.lock().unwrap();
|
||||
match map.entry(module) {
|
||||
Entry::Occupied(entry) => f(entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
let mut visitor = TestItemNamesVisitor { tcx, names: Vec::new() };
|
||||
tcx.hir().visit_item_likes_in_module(module, &mut visitor);
|
||||
visitor.names.sort_unstable();
|
||||
f(&*entry.insert(visitor.names))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the function containing the given `HirId` is a `#[test]` function
|
||||
///
|
||||
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
|
||||
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||
let names: Vec<_> = tcx
|
||||
.hir()
|
||||
.parent_iter(id)
|
||||
// Since you can nest functions we need to collect all until we leave
|
||||
// function scope
|
||||
.filter_map(|(_id, node)| {
|
||||
if let Node::Item(item) = node {
|
||||
if let ItemKind::Fn(_, _, _) = item.kind {
|
||||
return Some(item.ident.name);
|
||||
with_test_item_names(tcx, tcx.parent_module(id), |names| {
|
||||
tcx.hir()
|
||||
.parent_iter(id)
|
||||
// Since you can nest functions we need to collect all until we leave
|
||||
// function scope
|
||||
.any(|(_id, node)| {
|
||||
if let Node::Item(item) = node {
|
||||
if let ItemKind::Fn(_, _, _) = item.kind {
|
||||
// Note that we have sorted the item names in the visitor,
|
||||
// so the binary_search gets the same as `contains`, but faster.
|
||||
return names.binary_search(&item.ident.name).is_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
let parent_mod = tcx.parent_module(id);
|
||||
let mut vis = VisitConstTestStruct {
|
||||
tcx,
|
||||
names,
|
||||
found: false,
|
||||
};
|
||||
tcx.hir().visit_item_likes_in_module(parent_mod, &mut vis);
|
||||
vis.found
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether item either has `test` attribute applied, or
|
||||
|
||||
@@ -15,6 +15,7 @@ impl StrIndex {
|
||||
/// Returns the index of the character after the first camel-case component of `s`.
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{camel_case_until, StrIndex};
|
||||
/// assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6));
|
||||
/// assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0));
|
||||
/// assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3));
|
||||
@@ -55,9 +56,10 @@ pub fn camel_case_until(s: &str) -> StrIndex {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns index of the last camel-case component of `s`.
|
||||
/// Returns index of the first camel-case component of `s`.
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{camel_case_start, StrIndex};
|
||||
/// assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0));
|
||||
/// assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3));
|
||||
/// assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4));
|
||||
@@ -66,19 +68,37 @@ pub fn camel_case_until(s: &str) -> StrIndex {
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn camel_case_start(s: &str) -> StrIndex {
|
||||
camel_case_start_from_idx(s, 0)
|
||||
}
|
||||
|
||||
/// Returns `StrIndex` of the last camel-case component of `s[idx..]`.
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{camel_case_start_from_idx, StrIndex};
|
||||
/// assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0));
|
||||
/// assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3));
|
||||
/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0));
|
||||
/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3));
|
||||
/// assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7));
|
||||
/// ```
|
||||
pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex {
|
||||
let char_count = s.chars().count();
|
||||
let range = 0..char_count;
|
||||
let mut iter = range.rev().zip(s.char_indices().rev());
|
||||
if let Some((char_index, (_, first))) = iter.next() {
|
||||
if let Some((_, (_, first))) = iter.next() {
|
||||
if !first.is_lowercase() {
|
||||
return StrIndex::new(char_index, s.len());
|
||||
return StrIndex::new(char_count, s.len());
|
||||
}
|
||||
} else {
|
||||
return StrIndex::new(char_count, s.len());
|
||||
}
|
||||
|
||||
let mut down = true;
|
||||
let mut last_index = StrIndex::new(char_count, s.len());
|
||||
for (char_index, (byte_index, c)) in iter {
|
||||
if byte_index < start_idx {
|
||||
break;
|
||||
}
|
||||
if down {
|
||||
if c.is_uppercase() {
|
||||
down = false;
|
||||
@@ -96,9 +116,55 @@ pub fn camel_case_start(s: &str) -> StrIndex {
|
||||
return last_index;
|
||||
}
|
||||
}
|
||||
|
||||
last_index
|
||||
}
|
||||
|
||||
/// Get the indexes of camel case components of a string `s`
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{camel_case_indices, StrIndex};
|
||||
/// assert_eq!(
|
||||
/// camel_case_indices("AbcDef"),
|
||||
/// vec![StrIndex::new(0, 0), StrIndex::new(3, 3), StrIndex::new(6, 6)]
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// camel_case_indices("abcDef"),
|
||||
/// vec![StrIndex::new(3, 3), StrIndex::new(6, 6)]
|
||||
/// );
|
||||
/// ```
|
||||
pub fn camel_case_indices(s: &str) -> Vec<StrIndex> {
|
||||
let mut result = Vec::new();
|
||||
let mut str_idx = camel_case_start(s);
|
||||
|
||||
while str_idx.byte_index < s.len() {
|
||||
let next_idx = str_idx.byte_index + 1;
|
||||
result.push(str_idx);
|
||||
str_idx = camel_case_start_from_idx(s, next_idx);
|
||||
}
|
||||
result.push(str_idx);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Split camel case string into a vector of its components
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{camel_case_split, StrIndex};
|
||||
/// assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]);
|
||||
/// ```
|
||||
pub fn camel_case_split(s: &str) -> Vec<&str> {
|
||||
let mut offsets = camel_case_indices(s)
|
||||
.iter()
|
||||
.map(|e| e.byte_index)
|
||||
.collect::<Vec<usize>>();
|
||||
if offsets[0] != 0 {
|
||||
offsets.insert(0, 0);
|
||||
}
|
||||
|
||||
offsets.windows(2).map(|w| &s[w[0]..w[1]]).collect()
|
||||
}
|
||||
|
||||
/// Dealing with sting comparison can be complicated, this struct ensures that both the
|
||||
/// character and byte count are provided for correct indexing.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
@@ -116,6 +182,7 @@ impl StrCount {
|
||||
/// Returns the number of chars that match from the start
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{count_match_start, StrCount};
|
||||
/// assert_eq!(count_match_start("hello_mouse", "hello_penguin"), StrCount::new(6, 6));
|
||||
/// assert_eq!(count_match_start("hello_clippy", "bye_bugs"), StrCount::new(0, 0));
|
||||
/// assert_eq!(count_match_start("hello_world", "hello_world"), StrCount::new(11, 11));
|
||||
@@ -141,6 +208,7 @@ pub fn count_match_start(str1: &str, str2: &str) -> StrCount {
|
||||
/// Returns the number of chars and bytes that match from the end
|
||||
///
|
||||
/// ```
|
||||
/// # use clippy_utils::str_utils::{count_match_end, StrCount};
|
||||
/// assert_eq!(count_match_end("hello_cat", "bye_cat"), StrCount::new(4, 4));
|
||||
/// assert_eq!(count_match_end("if_item_thing", "enum_value"), StrCount::new(0, 0));
|
||||
/// assert_eq!(count_match_end("Clippy", "Clippy"), StrCount::new(6, 6));
|
||||
@@ -227,4 +295,31 @@ mod test {
|
||||
fn until_caps() {
|
||||
assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn camel_case_start_from_idx_full() {
|
||||
assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0));
|
||||
assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3));
|
||||
assert_eq!(camel_case_start_from_idx("AbcDef", 4), StrIndex::new(6, 6));
|
||||
assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0));
|
||||
assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3));
|
||||
assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn camel_case_indices_full() {
|
||||
assert_eq!(camel_case_indices("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn camel_case_split_full() {
|
||||
assert_eq!(camel_case_split("A"), vec!["A"]);
|
||||
assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]);
|
||||
assert_eq!(camel_case_split("Abc"), vec!["Abc"]);
|
||||
assert_eq!(camel_case_split("abcDef"), vec!["abc", "Def"]);
|
||||
assert_eq!(
|
||||
camel_case_split("\u{f6}\u{f6}AabABcd"),
|
||||
vec!["\u{f6}\u{f6}", "Aab", "A", "Bcd"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Contains utility functions to generate suggestions.
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::source::{
|
||||
snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
|
||||
};
|
||||
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite};
|
||||
use crate::{get_parent_expr_for_hir, higher};
|
||||
use rustc_ast::util::parser::AssocOp;
|
||||
use rustc_ast::{ast, token};
|
||||
@@ -33,7 +31,7 @@ pub enum Sugg<'a> {
|
||||
MaybeParen(Cow<'a, str>),
|
||||
/// A binary operator expression, including `as`-casts and explicit type
|
||||
/// coercion.
|
||||
BinOp(AssocOp, Cow<'a, str>),
|
||||
BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
|
||||
}
|
||||
|
||||
/// Literal constant `0`, for convenience.
|
||||
@@ -46,7 +44,8 @@ pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
|
||||
impl Display for Sugg<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match *self {
|
||||
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
|
||||
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
|
||||
Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,10 +54,8 @@ impl Display for Sugg<'_> {
|
||||
impl<'a> Sugg<'a> {
|
||||
/// Prepare a suggestion from an expression.
|
||||
pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
|
||||
snippet_opt(cx, expr.span).map(|snippet| {
|
||||
let snippet = Cow::Owned(snippet);
|
||||
Self::hir_from_snippet(expr, snippet)
|
||||
})
|
||||
let get_snippet = |span| snippet(cx, span, "");
|
||||
snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet))
|
||||
}
|
||||
|
||||
/// Convenience function around `hir_opt` for suggestions with a default
|
||||
@@ -93,9 +90,8 @@ impl<'a> Sugg<'a> {
|
||||
|
||||
/// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
|
||||
pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
|
||||
let snippet = snippet_with_macro_callsite(cx, expr.span, default);
|
||||
|
||||
Self::hir_from_snippet(expr, snippet)
|
||||
let get_snippet = |span| snippet_with_macro_callsite(cx, span, default);
|
||||
Self::hir_from_snippet(expr, get_snippet)
|
||||
}
|
||||
|
||||
/// Same as `hir`, but first walks the span up to the given context. This will result in the
|
||||
@@ -112,24 +108,26 @@ impl<'a> Sugg<'a> {
|
||||
default: &'a str,
|
||||
applicability: &mut Applicability,
|
||||
) -> Self {
|
||||
let (snippet, in_macro) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
|
||||
|
||||
if in_macro {
|
||||
Sugg::NonParen(snippet)
|
||||
if expr.span.ctxt() == ctxt {
|
||||
Self::hir_from_snippet(expr, |span| snippet(cx, span, default))
|
||||
} else {
|
||||
Self::hir_from_snippet(expr, snippet)
|
||||
let snip = snippet_with_applicability(cx, expr.span, default, applicability);
|
||||
Sugg::NonParen(snip)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
|
||||
/// function variants of `Sugg`, since these use different snippet functions.
|
||||
fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
|
||||
fn hir_from_snippet(expr: &hir::Expr<'_>, get_snippet: impl Fn(Span) -> Cow<'a, str>) -> Self {
|
||||
if let Some(range) = higher::Range::hir(expr) {
|
||||
let op = match range.limits {
|
||||
ast::RangeLimits::HalfOpen => AssocOp::DotDot,
|
||||
ast::RangeLimits::Closed => AssocOp::DotDotEq,
|
||||
};
|
||||
return Sugg::BinOp(op, snippet);
|
||||
let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
|
||||
let end = range.end.map_or("".into(), |expr| get_snippet(expr.span));
|
||||
|
||||
return Sugg::BinOp(op, start, end);
|
||||
}
|
||||
|
||||
match expr.kind {
|
||||
@@ -139,7 +137,7 @@ impl<'a> Sugg<'a> {
|
||||
| hir::ExprKind::Let(..)
|
||||
| hir::ExprKind::Closure(..)
|
||||
| hir::ExprKind::Unary(..)
|
||||
| hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
|
||||
| hir::ExprKind::Match(..) => Sugg::MaybeParen(get_snippet(expr.span)),
|
||||
hir::ExprKind::Continue(..)
|
||||
| hir::ExprKind::Yield(..)
|
||||
| hir::ExprKind::Array(..)
|
||||
@@ -160,12 +158,20 @@ impl<'a> Sugg<'a> {
|
||||
| hir::ExprKind::Struct(..)
|
||||
| hir::ExprKind::Tup(..)
|
||||
| hir::ExprKind::DropTemps(_)
|
||||
| hir::ExprKind::Err => Sugg::NonParen(snippet),
|
||||
hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
|
||||
hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
|
||||
hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node.into()), snippet),
|
||||
hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
|
||||
hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
|
||||
| hir::ExprKind::Err => Sugg::NonParen(get_snippet(expr.span)),
|
||||
hir::ExprKind::Assign(lhs, rhs, _) => {
|
||||
Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span))
|
||||
},
|
||||
hir::ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span))
|
||||
},
|
||||
hir::ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp(
|
||||
AssocOp::from_ast_binop(op.node.into()),
|
||||
get_snippet(lhs.span),
|
||||
get_snippet(rhs.span),
|
||||
),
|
||||
hir::ExprKind::Cast(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)),
|
||||
hir::ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::Colon, get_snippet(lhs.span), get_snippet(ty.span)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,10 +179,12 @@ impl<'a> Sugg<'a> {
|
||||
pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
|
||||
let snippet = if expr.span.from_expansion() {
|
||||
snippet_with_macro_callsite(cx, expr.span, default)
|
||||
} else {
|
||||
snippet(cx, expr.span, default)
|
||||
let get_whole_snippet = || {
|
||||
if expr.span.from_expansion() {
|
||||
snippet_with_macro_callsite(cx, expr.span, default)
|
||||
} else {
|
||||
snippet(cx, expr.span, default)
|
||||
}
|
||||
};
|
||||
|
||||
match expr.kind {
|
||||
@@ -186,7 +194,7 @@ impl<'a> Sugg<'a> {
|
||||
| ast::ExprKind::If(..)
|
||||
| ast::ExprKind::Let(..)
|
||||
| ast::ExprKind::Unary(..)
|
||||
| ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
|
||||
| ast::ExprKind::Match(..) => Sugg::MaybeParen(get_whole_snippet()),
|
||||
ast::ExprKind::Async(..)
|
||||
| ast::ExprKind::Block(..)
|
||||
| ast::ExprKind::Break(..)
|
||||
@@ -215,14 +223,42 @@ impl<'a> Sugg<'a> {
|
||||
| ast::ExprKind::Array(..)
|
||||
| ast::ExprKind::While(..)
|
||||
| ast::ExprKind::Await(..)
|
||||
| ast::ExprKind::Err => Sugg::NonParen(snippet),
|
||||
ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
|
||||
ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
|
||||
ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
|
||||
ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
|
||||
ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
|
||||
ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
|
||||
ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
|
||||
| ast::ExprKind::Err => Sugg::NonParen(get_whole_snippet()),
|
||||
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
|
||||
AssocOp::DotDot,
|
||||
lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)),
|
||||
rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)),
|
||||
),
|
||||
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
|
||||
AssocOp::DotDotEq,
|
||||
lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)),
|
||||
rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)),
|
||||
),
|
||||
ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
|
||||
AssocOp::Assign,
|
||||
snippet(cx, lhs.span, default),
|
||||
snippet(cx, rhs.span, default),
|
||||
),
|
||||
ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
|
||||
astbinop2assignop(op),
|
||||
snippet(cx, lhs.span, default),
|
||||
snippet(cx, rhs.span, default),
|
||||
),
|
||||
ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
|
||||
AssocOp::from_ast_binop(op.node),
|
||||
snippet(cx, lhs.span, default),
|
||||
snippet(cx, rhs.span, default),
|
||||
),
|
||||
ast::ExprKind::Cast(ref lhs, ref ty) => Sugg::BinOp(
|
||||
AssocOp::As,
|
||||
snippet(cx, lhs.span, default),
|
||||
snippet(cx, ty.span, default),
|
||||
),
|
||||
ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
|
||||
AssocOp::Colon,
|
||||
snippet(cx, lhs.span, default),
|
||||
snippet(cx, ty.span, default),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,17 +342,51 @@ impl<'a> Sugg<'a> {
|
||||
Sugg::NonParen(format!("({})", sugg).into())
|
||||
}
|
||||
},
|
||||
Sugg::BinOp(_, sugg) => {
|
||||
if has_enclosing_paren(&sugg) {
|
||||
Sugg::NonParen(sugg)
|
||||
} else {
|
||||
Sugg::NonParen(format!("({})", sugg).into())
|
||||
}
|
||||
Sugg::BinOp(op, lhs, rhs) => {
|
||||
let sugg = binop_to_string(op, &lhs, &rhs);
|
||||
Sugg::NonParen(format!("({})", sugg).into())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a string from the operator and both sides.
|
||||
fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
|
||||
match op {
|
||||
AssocOp::Add
|
||||
| AssocOp::Subtract
|
||||
| AssocOp::Multiply
|
||||
| AssocOp::Divide
|
||||
| AssocOp::Modulus
|
||||
| AssocOp::LAnd
|
||||
| AssocOp::LOr
|
||||
| AssocOp::BitXor
|
||||
| AssocOp::BitAnd
|
||||
| AssocOp::BitOr
|
||||
| AssocOp::ShiftLeft
|
||||
| AssocOp::ShiftRight
|
||||
| AssocOp::Equal
|
||||
| AssocOp::Less
|
||||
| AssocOp::LessEqual
|
||||
| AssocOp::NotEqual
|
||||
| AssocOp::Greater
|
||||
| AssocOp::GreaterEqual => format!(
|
||||
"{} {} {}",
|
||||
lhs,
|
||||
op.to_ast_binop().expect("Those are AST ops").to_string(),
|
||||
rhs
|
||||
),
|
||||
AssocOp::Assign => format!("{} = {}", lhs, rhs),
|
||||
AssocOp::AssignOp(op) => {
|
||||
format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs)
|
||||
},
|
||||
AssocOp::As => format!("{} as {}", lhs, rhs),
|
||||
AssocOp::DotDot => format!("{}..{}", lhs, rhs),
|
||||
AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
|
||||
AssocOp::Colon => format!("{}: {}", lhs, rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `sugg` is enclosed in parenthesis.
|
||||
fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
|
||||
let mut chars = sugg.as_ref().chars();
|
||||
@@ -391,10 +461,25 @@ impl Neg for Sugg<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Sugg<'_> {
|
||||
type Output = Sugg<'static>;
|
||||
fn not(self) -> Sugg<'static> {
|
||||
make_unop("!", self)
|
||||
impl Not for Sugg<'a> {
|
||||
type Output = Sugg<'a>;
|
||||
fn not(self) -> Sugg<'a> {
|
||||
use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
|
||||
|
||||
if let Sugg::BinOp(op, lhs, rhs) = self {
|
||||
let to_op = match op {
|
||||
Equal => NotEqual,
|
||||
NotEqual => Equal,
|
||||
Less => GreaterEqual,
|
||||
GreaterEqual => Less,
|
||||
Greater => LessEqual,
|
||||
LessEqual => Greater,
|
||||
_ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
|
||||
};
|
||||
Sugg::BinOp(to_op, lhs, rhs)
|
||||
} else {
|
||||
make_unop("!", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,53 +548,21 @@ pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static>
|
||||
|| is_shift(other) && is_arith(op)
|
||||
}
|
||||
|
||||
let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
|
||||
let lhs_paren = if let Sugg::BinOp(lop, _, _) = *lhs {
|
||||
needs_paren(op, lop, Associativity::Left)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
|
||||
let rhs_paren = if let Sugg::BinOp(rop, _, _) = *rhs {
|
||||
needs_paren(op, rop, Associativity::Right)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let lhs = ParenHelper::new(lhs_paren, lhs);
|
||||
let rhs = ParenHelper::new(rhs_paren, rhs);
|
||||
let sugg = match op {
|
||||
AssocOp::Add
|
||||
| AssocOp::BitAnd
|
||||
| AssocOp::BitOr
|
||||
| AssocOp::BitXor
|
||||
| AssocOp::Divide
|
||||
| AssocOp::Equal
|
||||
| AssocOp::Greater
|
||||
| AssocOp::GreaterEqual
|
||||
| AssocOp::LAnd
|
||||
| AssocOp::LOr
|
||||
| AssocOp::Less
|
||||
| AssocOp::LessEqual
|
||||
| AssocOp::Modulus
|
||||
| AssocOp::Multiply
|
||||
| AssocOp::NotEqual
|
||||
| AssocOp::ShiftLeft
|
||||
| AssocOp::ShiftRight
|
||||
| AssocOp::Subtract => format!(
|
||||
"{} {} {}",
|
||||
lhs,
|
||||
op.to_ast_binop().expect("Those are AST ops").to_string(),
|
||||
rhs
|
||||
),
|
||||
AssocOp::Assign => format!("{} = {}", lhs, rhs),
|
||||
AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
|
||||
AssocOp::As => format!("{} as {}", lhs, rhs),
|
||||
AssocOp::DotDot => format!("{}..{}", lhs, rhs),
|
||||
AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
|
||||
AssocOp::Colon => format!("{}: {}", lhs, rhs),
|
||||
};
|
||||
|
||||
Sugg::BinOp(op, sugg.into())
|
||||
let lhs = ParenHelper::new(lhs_paren, lhs).to_string();
|
||||
let rhs = ParenHelper::new(rhs_paren, rhs).to_string();
|
||||
Sugg::BinOp(op, lhs.into(), rhs.into())
|
||||
}
|
||||
|
||||
/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
|
||||
@@ -1007,10 +1060,32 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn binop_maybe_par() {
|
||||
let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into());
|
||||
let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into());
|
||||
assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
|
||||
|
||||
let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1) + (1 + 1)".into());
|
||||
let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into());
|
||||
assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
|
||||
}
|
||||
#[test]
|
||||
fn not_op() {
|
||||
use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual};
|
||||
|
||||
fn test_not(op: AssocOp, correct: &str) {
|
||||
let sugg = Sugg::BinOp(op, "x".into(), "y".into());
|
||||
assert_eq!((!sugg).to_string(), correct);
|
||||
}
|
||||
|
||||
// Invert the comparison operator.
|
||||
test_not(Equal, "x != y");
|
||||
test_not(NotEqual, "x == y");
|
||||
test_not(Less, "x >= y");
|
||||
test_not(LessEqual, "x > y");
|
||||
test_not(Greater, "x <= y");
|
||||
test_not(GreaterEqual, "x < y");
|
||||
|
||||
// Other operators are inverted like !(..).
|
||||
test_not(Add, "!(x + y)");
|
||||
test_not(LAnd, "!(x && y)");
|
||||
test_not(LOr, "!(x || y)");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user