rust-analyzer/crates/stdx/src/lib.rs
Johannes Altmanninger 30b992e95a Deduplicate references to macro argument
Commit 6a06f6f72 (Deduplicate reference search results, 2022-11-07) deduplicates references
within each definition.

There is an edge case when requesting references of a macro argument.  Apparently, our
descend_into_macros() stanza in references.rs produces a cartesian product of
- references inside the macro times
- times references outside the macro.

Since the above deduplication only applies to the references within a single definition, we
return them all, leading to many redundant references.

Work around this by deduplicating definitions as well.  Perhaps there is a better fix to not
produce this cartesian product in the first place; but I think at least for definitions the
problem would remain; a macro can contain multiple definitions of the same name, but since the
navigation target will be the unresolved location, it's the same for all of them.

We can't use unique() because we don't want to drop references that don't have a declaration
(though I dont' have an example for this case).

I discovered this working with the "bitflags" macro from the crate of the same name.

Fixes #16357
2024-02-19 12:23:59 +01:00

357 lines
8.5 KiB
Rust

//! Missing batteries for standard libraries.
#![warn(rust_2018_idioms, unused_lifetimes)]
use std::io as sio;
use std::process::Command;
use std::{cmp::Ordering, ops, time::Instant};
pub mod anymap;
mod macros;
pub mod non_empty_vec;
pub mod panic_context;
pub mod process;
pub mod rand;
pub mod thread;
pub use always_assert::{always, never};
pub use itertools;
#[inline(always)]
pub fn is_ci() -> bool {
option_env!("CI").is_some()
}
#[must_use]
#[allow(clippy::print_stderr)]
pub fn timeit(label: &'static str) -> impl Drop {
let start = Instant::now();
defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
}
/// Prints backtrace to stderr, useful for debugging.
#[allow(clippy::print_stderr)]
pub fn print_backtrace() {
#[cfg(feature = "backtrace")]
eprintln!("{:?}", backtrace::Backtrace::new());
#[cfg(not(feature = "backtrace"))]
eprintln!(
r#"Enable the backtrace feature.
Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
"#
);
}
pub trait TupleExt {
type Head;
type Tail;
fn head(self) -> Self::Head;
fn tail(self) -> Self::Tail;
}
impl<T, U> TupleExt for (T, U) {
type Head = T;
type Tail = U;
fn head(self) -> Self::Head {
self.0
}
fn tail(self) -> Self::Tail {
self.1
}
}
impl<T, U, V> TupleExt for (T, U, V) {
type Head = T;
type Tail = V;
fn head(self) -> Self::Head {
self.0
}
fn tail(self) -> Self::Tail {
self.2
}
}
pub fn to_lower_snake_case(s: &str) -> String {
to_snake_case(s, char::to_lowercase)
}
pub fn to_upper_snake_case(s: &str) -> String {
to_snake_case(s, char::to_uppercase)
}
// Code partially taken from rust/compiler/rustc_lint/src/nonstandard_style.rs
// commit: 9626f2b
fn to_snake_case<F, I>(mut s: &str, change_case: F) -> String
where
F: Fn(char) -> I,
I: Iterator<Item = char>,
{
let mut words = vec![];
// Preserve leading underscores
s = s.trim_start_matches(|c: char| {
if c == '_' {
words.push(String::new());
true
} else {
false
}
});
for s in s.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(change_case(ch));
}
words.push(buf);
}
words.join("_")
}
// Taken from rustc.
pub fn to_camel_case(ident: &str) -> String {
ident
.trim_matches('_')
.split('_')
.filter(|component| !component.is_empty())
.map(|component| {
let mut camel_cased_component = String::with_capacity(component.len());
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.extend(c.to_uppercase());
} else {
camel_cased_component.extend(c.to_lowercase());
}
prev_is_lower_case = c.is_lowercase();
new_word = false;
}
camel_cased_component
})
.fold((String::new(), None), |(acc, prev): (_, Option<String>), next| {
// separate two components with an underscore if their boundary cannot
// be distinguished using an uppercase/lowercase case distinction
let join = prev
.and_then(|prev| {
let f = next.chars().next()?;
let l = prev.chars().last()?;
Some(!char_has_case(l) && !char_has_case(f))
})
.unwrap_or(false);
(acc + if join { "_" } else { "" } + &next, Some(next))
})
.0
}
// Taken from rustc.
pub fn char_has_case(c: char) -> bool {
c.is_lowercase() || c.is_uppercase()
}
pub fn is_upper_snake_case(s: &str) -> bool {
s.chars().all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
}
pub fn replace(buf: &mut String, from: char, to: &str) {
if !buf.contains(from) {
return;
}
// FIXME: do this in place.
*buf = buf.replace(from, to);
}
pub fn trim_indent(mut text: &str) -> String {
if text.starts_with('\n') {
text = &text[1..];
}
let indent = text
.lines()
.filter(|it| !it.trim().is_empty())
.map(|it| it.len() - it.trim_start().len())
.min()
.unwrap_or(0);
text.split_inclusive('\n')
.map(
|line| {
if line.len() <= indent {
line.trim_start_matches(' ')
} else {
&line[indent..]
}
},
)
.collect()
}
pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
where
F: FnMut(&T) -> Ordering,
{
let start = slice.partition_point(|it| key(it) == Ordering::Less);
let len = slice[start..].partition_point(|it| key(it) == Ordering::Equal);
start..start + len
}
#[must_use]
pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
struct D<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for D<F> {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f();
}
}
}
D(Some(f))
}
/// A [`std::process::Child`] wrapper that will kill the child on drop.
#[cfg_attr(not(target_arch = "wasm32"), repr(transparent))]
#[derive(Debug)]
pub struct JodChild(pub std::process::Child);
impl ops::Deref for JodChild {
type Target = std::process::Child;
fn deref(&self) -> &std::process::Child {
&self.0
}
}
impl ops::DerefMut for JodChild {
fn deref_mut(&mut self) -> &mut std::process::Child {
&mut self.0
}
}
impl Drop for JodChild {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
impl JodChild {
pub fn spawn(mut command: Command) -> sio::Result<Self> {
command.spawn().map(Self)
}
pub fn into_inner(self) -> std::process::Child {
if cfg!(target_arch = "wasm32") {
panic!("no processes on wasm");
}
// SAFETY: repr transparent, except on WASM
unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
}
}
// feature: iter_order_by
// Iterator::eq_by
pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool
where
I: IntoIterator,
I2: IntoIterator,
F: FnMut(I2::Item, I::Item) -> bool,
{
let mut other = other.into_iter();
let mut this = this.into_iter();
loop {
let x = match this.next() {
None => return other.next().is_none(),
Some(val) => val,
};
let y = match other.next() {
None => return false,
Some(val) => val,
};
if !eq(x, y) {
return false;
}
}
}
/// Returns all final segments of the argument, longest first.
pub fn slice_tails<T>(this: &[T]) -> impl Iterator<Item = &[T]> {
(0..this.len()).map(|i| &this[i..])
}
pub trait IsNoneOr {
type Type;
#[allow(clippy::wrong_self_convention)]
fn is_none_or(self, s: impl FnOnce(Self::Type) -> bool) -> bool;
}
#[allow(unstable_name_collisions)]
impl<T> IsNoneOr for Option<T> {
type Type = T;
fn is_none_or(self, f: impl FnOnce(T) -> bool) -> bool {
match self {
Some(v) => f(v),
None => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trim_indent() {
assert_eq!(trim_indent(""), "");
assert_eq!(
trim_indent(
"
hello
world
"
),
"hello\nworld\n"
);
assert_eq!(
trim_indent(
"
hello
world"
),
"hello\nworld"
);
assert_eq!(trim_indent(" hello\n world\n"), "hello\nworld\n");
assert_eq!(
trim_indent(
"
fn main() {
return 92;
}
"
),
"fn main() {\n return 92;\n}\n"
);
}
}