summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/stdx
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/stdx')
-rw-r--r--src/tools/rust-analyzer/crates/stdx/Cargo.toml24
-rw-r--r--src/tools/rust-analyzer/crates/stdx/src/lib.rs247
-rw-r--r--src/tools/rust-analyzer/crates/stdx/src/macros.rs47
-rw-r--r--src/tools/rust-analyzer/crates/stdx/src/non_empty_vec.rs39
-rw-r--r--src/tools/rust-analyzer/crates/stdx/src/panic_context.rs49
-rw-r--r--src/tools/rust-analyzer/crates/stdx/src/process.rs267
6 files changed, 673 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/stdx/Cargo.toml b/src/tools/rust-analyzer/crates/stdx/Cargo.toml
new file mode 100644
index 000000000..092b99ae5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "stdx"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+libc = "0.2.126"
+backtrace = { version = "0.3.65", optional = true }
+always-assert = { version = "0.1.2", features = ["log"] }
+# Think twice before adding anything here
+
+[target.'cfg(windows)'.dependencies]
+miow = "0.4.0"
+winapi = { version = "0.3.9", features = ["winerror"] }
+
+[features]
+# Uncomment to enable for the whole crate graph
+# default = [ "backtrace" ]
diff --git a/src/tools/rust-analyzer/crates/stdx/src/lib.rs b/src/tools/rust-analyzer/crates/stdx/src/lib.rs
new file mode 100644
index 000000000..b4d45206c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/src/lib.rs
@@ -0,0 +1,247 @@
+//! Missing batteries for standard libraries.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+use std::process::Command;
+use std::{cmp::Ordering, ops, time::Instant};
+use std::{io as sio, iter};
+
+mod macros;
+pub mod process;
+pub mod panic_context;
+pub mod non_empty_vec;
+
+pub use always_assert::{always, never};
+
+#[inline(always)]
+pub fn is_ci() -> bool {
+ option_env!("CI").is_some()
+}
+
+#[must_use]
+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.
+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 fn to_lower_snake_case(s: &str) -> String {
+ to_snake_case(s, char::to_ascii_lowercase)
+}
+pub fn to_upper_snake_case(s: &str) -> String {
+ to_snake_case(s, char::to_ascii_uppercase)
+}
+
+// Code partially taken from rust/compiler/rustc_lint/src/nonstandard_style.rs
+// commit: 9626f2b
+fn to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String {
+ 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(iter::once(change_case(&ch)));
+ }
+
+ words.push(buf);
+ }
+
+ words.join("_")
+}
+
+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..])
+}
+
+#[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"
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/stdx/src/macros.rs b/src/tools/rust-analyzer/crates/stdx/src/macros.rs
new file mode 100644
index 000000000..d91fc690c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/src/macros.rs
@@ -0,0 +1,47 @@
+//! Convenience macros.
+
+#[macro_export]
+macro_rules! eprintln {
+ ($($tt:tt)*) => {{
+ if $crate::is_ci() {
+ panic!("Forgot to remove debug-print?")
+ }
+ std::eprintln!($($tt)*)
+ }}
+}
+
+/// Appends formatted string to a `String`.
+#[macro_export]
+macro_rules! format_to {
+ ($buf:expr) => ();
+ ($buf:expr, $lit:literal $($arg:tt)*) => {
+ { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); }
+ };
+}
+
+/// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums
+///
+/// # Example
+///
+/// ```rust
+/// impl_from!(Struct, Union, Enum for Adt);
+/// ```
+#[macro_export]
+macro_rules! impl_from {
+ ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => {
+ $(
+ impl From<$variant> for $enum {
+ fn from(it: $variant) -> $enum {
+ $enum::$variant(it)
+ }
+ }
+ $($(
+ impl From<$sub_variant> for $enum {
+ fn from(it: $sub_variant) -> $enum {
+ $enum::$variant($variant::$sub_variant(it))
+ }
+ }
+ )*)?
+ )*
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/stdx/src/non_empty_vec.rs b/src/tools/rust-analyzer/crates/stdx/src/non_empty_vec.rs
new file mode 100644
index 000000000..342194c78
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/src/non_empty_vec.rs
@@ -0,0 +1,39 @@
+//! See [`NonEmptyVec`].
+
+/// A [`Vec`] that is guaranteed to at least contain one element.
+pub struct NonEmptyVec<T> {
+ first: T,
+ rest: Vec<T>,
+}
+
+impl<T> NonEmptyVec<T> {
+ #[inline]
+ pub fn new(first: T) -> Self {
+ NonEmptyVec { first, rest: Vec::new() }
+ }
+
+ #[inline]
+ pub fn last_mut(&mut self) -> &mut T {
+ self.rest.last_mut().unwrap_or(&mut self.first)
+ }
+
+ #[inline]
+ pub fn pop(&mut self) -> Option<T> {
+ self.rest.pop()
+ }
+
+ #[inline]
+ pub fn push(&mut self, value: T) {
+ self.rest.push(value)
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ 1 + self.rest.len()
+ }
+
+ #[inline]
+ pub fn into_last(mut self) -> T {
+ self.rest.pop().unwrap_or(self.first)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/stdx/src/panic_context.rs b/src/tools/rust-analyzer/crates/stdx/src/panic_context.rs
new file mode 100644
index 000000000..f8fafc5a6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/src/panic_context.rs
@@ -0,0 +1,49 @@
+//! A micro-crate to enhance panic messages with context info.
+//!
+//! FIXME: upstream to <https://github.com/kriomant/panic-context> ?
+
+use std::{cell::RefCell, panic, sync::Once};
+
+pub fn enter(context: String) -> PanicContext {
+ static ONCE: Once = Once::new();
+ ONCE.call_once(PanicContext::init);
+
+ with_ctx(|ctx| ctx.push(context));
+ PanicContext { _priv: () }
+}
+
+#[must_use]
+pub struct PanicContext {
+ _priv: (),
+}
+
+impl PanicContext {
+ fn init() {
+ let default_hook = panic::take_hook();
+ let hook = move |panic_info: &panic::PanicInfo<'_>| {
+ with_ctx(|ctx| {
+ if !ctx.is_empty() {
+ eprintln!("Panic context:");
+ for frame in ctx.iter() {
+ eprintln!("> {}\n", frame);
+ }
+ }
+ default_hook(panic_info);
+ });
+ };
+ panic::set_hook(Box::new(hook));
+ }
+}
+
+impl Drop for PanicContext {
+ fn drop(&mut self) {
+ with_ctx(|ctx| assert!(ctx.pop().is_some()));
+ }
+}
+
+fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
+ thread_local! {
+ static CTX: RefCell<Vec<String>> = RefCell::new(Vec::new());
+ }
+ CTX.with(|ctx| f(&mut *ctx.borrow_mut()));
+}
diff --git a/src/tools/rust-analyzer/crates/stdx/src/process.rs b/src/tools/rust-analyzer/crates/stdx/src/process.rs
new file mode 100644
index 000000000..e5aa34365
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/stdx/src/process.rs
@@ -0,0 +1,267 @@
+//! Read both stdout and stderr of child without deadlocks.
+//!
+//! <https://github.com/rust-lang/cargo/blob/905af549966f23a9288e9993a85d1249a5436556/crates/cargo-util/src/read2.rs>
+//! <https://github.com/rust-lang/cargo/blob/58a961314437258065e23cb6316dfc121d96fb71/crates/cargo-util/src/process_builder.rs#L231>
+
+use std::{
+ io,
+ process::{ChildStderr, ChildStdout, Command, Output, Stdio},
+};
+
+use crate::JodChild;
+
+pub fn streaming_output(
+ out: ChildStdout,
+ err: ChildStderr,
+ on_stdout_line: &mut dyn FnMut(&str),
+ on_stderr_line: &mut dyn FnMut(&str),
+) -> io::Result<(Vec<u8>, Vec<u8>)> {
+ let mut stdout = Vec::new();
+ let mut stderr = Vec::new();
+
+ imp::read2(out, err, &mut |is_out, data, eof| {
+ let idx = if eof {
+ data.len()
+ } else {
+ match data.iter().rposition(|b| *b == b'\n') {
+ Some(i) => i + 1,
+ None => return,
+ }
+ };
+ {
+ // scope for new_lines
+ let new_lines = {
+ let dst = if is_out { &mut stdout } else { &mut stderr };
+ let start = dst.len();
+ let data = data.drain(..idx);
+ dst.extend(data);
+ &dst[start..]
+ };
+ for line in String::from_utf8_lossy(new_lines).lines() {
+ if is_out {
+ on_stdout_line(line);
+ } else {
+ on_stderr_line(line);
+ }
+ }
+ }
+ })?;
+
+ Ok((stdout, stderr))
+}
+
+pub fn spawn_with_streaming_output(
+ mut cmd: Command,
+ on_stdout_line: &mut dyn FnMut(&str),
+ on_stderr_line: &mut dyn FnMut(&str),
+) -> io::Result<Output> {
+ let cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
+
+ let mut child = JodChild(cmd.spawn()?);
+ let (stdout, stderr) = streaming_output(
+ child.stdout.take().unwrap(),
+ child.stderr.take().unwrap(),
+ on_stdout_line,
+ on_stderr_line,
+ )?;
+ let status = child.wait()?;
+ Ok(Output { status, stdout, stderr })
+}
+
+#[cfg(unix)]
+mod imp {
+ use std::{
+ io::{self, prelude::*},
+ mem,
+ os::unix::prelude::*,
+ process::{ChildStderr, ChildStdout},
+ };
+
+ pub(crate) fn read2(
+ mut out_pipe: ChildStdout,
+ mut err_pipe: ChildStderr,
+ data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
+ ) -> io::Result<()> {
+ unsafe {
+ libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
+ libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
+ }
+
+ let mut out_done = false;
+ let mut err_done = false;
+ let mut out = Vec::new();
+ let mut err = Vec::new();
+
+ let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
+ fds[0].fd = out_pipe.as_raw_fd();
+ fds[0].events = libc::POLLIN;
+ fds[1].fd = err_pipe.as_raw_fd();
+ fds[1].events = libc::POLLIN;
+ let mut nfds = 2;
+ let mut errfd = 1;
+
+ while nfds > 0 {
+ // wait for either pipe to become readable using `select`
+ let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) };
+ if r == -1 {
+ let err = io::Error::last_os_error();
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue;
+ }
+ return Err(err);
+ }
+
+ // Read as much as we can from each pipe, ignoring EWOULDBLOCK or
+ // EAGAIN. If we hit EOF, then this will happen because the underlying
+ // reader will return Ok(0), in which case we'll see `Ok` ourselves. In
+ // this case we flip the other fd back into blocking mode and read
+ // whatever's leftover on that file descriptor.
+ let handle = |res: io::Result<_>| match res {
+ Ok(_) => Ok(true),
+ Err(e) => {
+ if e.kind() == io::ErrorKind::WouldBlock {
+ Ok(false)
+ } else {
+ Err(e)
+ }
+ }
+ };
+ if !err_done && fds[errfd].revents != 0 && handle(err_pipe.read_to_end(&mut err))? {
+ err_done = true;
+ nfds -= 1;
+ }
+ data(false, &mut err, err_done);
+ if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? {
+ out_done = true;
+ fds[0].fd = err_pipe.as_raw_fd();
+ errfd = 0;
+ nfds -= 1;
+ }
+ data(true, &mut out, out_done);
+ }
+ Ok(())
+ }
+}
+
+#[cfg(windows)]
+mod imp {
+ use std::{
+ io,
+ os::windows::prelude::*,
+ process::{ChildStderr, ChildStdout},
+ slice,
+ };
+
+ use miow::{
+ iocp::{CompletionPort, CompletionStatus},
+ pipe::NamedPipe,
+ Overlapped,
+ };
+ use winapi::shared::winerror::ERROR_BROKEN_PIPE;
+
+ struct Pipe<'a> {
+ dst: &'a mut Vec<u8>,
+ overlapped: Overlapped,
+ pipe: NamedPipe,
+ done: bool,
+ }
+
+ pub(crate) fn read2(
+ out_pipe: ChildStdout,
+ err_pipe: ChildStderr,
+ data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
+ ) -> io::Result<()> {
+ let mut out = Vec::new();
+ let mut err = Vec::new();
+
+ let port = CompletionPort::new(1)?;
+ port.add_handle(0, &out_pipe)?;
+ port.add_handle(1, &err_pipe)?;
+
+ unsafe {
+ let mut out_pipe = Pipe::new(out_pipe, &mut out);
+ let mut err_pipe = Pipe::new(err_pipe, &mut err);
+
+ out_pipe.read()?;
+ err_pipe.read()?;
+
+ let mut status = [CompletionStatus::zero(), CompletionStatus::zero()];
+
+ while !out_pipe.done || !err_pipe.done {
+ for status in port.get_many(&mut status, None)? {
+ if status.token() == 0 {
+ out_pipe.complete(status);
+ data(true, out_pipe.dst, out_pipe.done);
+ out_pipe.read()?;
+ } else {
+ err_pipe.complete(status);
+ data(false, err_pipe.dst, err_pipe.done);
+ err_pipe.read()?;
+ }
+ }
+ }
+
+ Ok(())
+ }
+ }
+
+ impl<'a> Pipe<'a> {
+ unsafe fn new<P: IntoRawHandle>(p: P, dst: &'a mut Vec<u8>) -> Pipe<'a> {
+ Pipe {
+ dst,
+ pipe: NamedPipe::from_raw_handle(p.into_raw_handle()),
+ overlapped: Overlapped::zero(),
+ done: false,
+ }
+ }
+
+ unsafe fn read(&mut self) -> io::Result<()> {
+ let dst = slice_to_end(self.dst);
+ match self.pipe.read_overlapped(dst, self.overlapped.raw()) {
+ Ok(_) => Ok(()),
+ Err(e) => {
+ if e.raw_os_error() == Some(ERROR_BROKEN_PIPE as i32) {
+ self.done = true;
+ Ok(())
+ } else {
+ Err(e)
+ }
+ }
+ }
+ }
+
+ unsafe fn complete(&mut self, status: &CompletionStatus) {
+ let prev = self.dst.len();
+ self.dst.set_len(prev + status.bytes_transferred() as usize);
+ if status.bytes_transferred() == 0 {
+ self.done = true;
+ }
+ }
+ }
+
+ unsafe fn slice_to_end(v: &mut Vec<u8>) -> &mut [u8] {
+ if v.capacity() == 0 {
+ v.reserve(16);
+ }
+ if v.capacity() == v.len() {
+ v.reserve(1);
+ }
+ slice::from_raw_parts_mut(v.as_mut_ptr().add(v.len()), v.capacity() - v.len())
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+mod imp {
+ use std::{
+ io,
+ process::{ChildStderr, ChildStdout},
+ };
+
+ pub(crate) fn read2(
+ _out_pipe: ChildStdout,
+ _err_pipe: ChildStderr,
+ _data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
+ ) -> io::Result<()> {
+ panic!("no processes on wasm")
+ }
+}