Skip to main content

elaborate/
lib.rs

1#![cfg_attr(dylint_lib = "general", allow(crate_wide_allow))]
2#![cfg_attr(dylint_lib = "supplementary", allow(nonexistent_path_in_comment))]
3#![allow(stable_features)]
4//
5// Unstable features
6#![cfg_attr(feature = "buf_read_has_data_left", feature(buf_read_has_data_left))]
7#![cfg_attr(feature = "bufread_skip_until", feature(bufread_skip_until))]
8#![cfg_attr(feature = "core_io_borrowed_buf", feature(core_io_borrowed_buf))]
9#![cfg_attr(feature = "dirfd", feature(dirfd))]
10#![cfg_attr(feature = "exit_status_error", feature(exit_status_error))]
11#![cfg_attr(feature = "file_buffered", feature(file_buffered))]
12#![cfg_attr(feature = "fs_set_times", feature(fs_set_times))]
13#![cfg_attr(feature = "gethostname", feature(gethostname))]
14#![cfg_attr(feature = "normalize_lexically", feature(normalize_lexically))]
15#![cfg_attr(feature = "panic_backtrace_config", feature(panic_backtrace_config))]
16#![cfg_attr(feature = "path_file_prefix", feature(path_file_prefix))]
17#![cfg_attr(feature = "path_absolute_method", feature(path_absolute_method))]
18#![cfg_attr(feature = "raw_os_error_ty", feature(raw_os_error_ty))]
19#![cfg_attr(feature = "read_array", feature(read_array))]
20#![cfg_attr(feature = "read_buf", feature(read_buf))]
21#![cfg_attr(feature = "read_buf_at", feature(read_buf_at))]
22#![cfg_attr(feature = "seek_stream_len", feature(seek_stream_len))]
23#![cfg_attr(
24    feature = "set_permissions_nofollow",
25    feature(set_permissions_nofollow)
26)]
27#![cfg_attr(feature = "tcp_linger", feature(tcp_linger))]
28#![cfg_attr(feature = "thread_spawn_unchecked", feature(thread_spawn_unchecked))]
29#![cfg_attr(feature = "write_all_vectored", feature(write_all_vectored))]
30//
31// Linux-specific unstable features
32#![cfg_attr(
33    all(target_os = "linux", feature = "linux_pidfd"),
34    feature(linux_pidfd)
35)]
36#![cfg_attr(
37    all(target_os = "linux", feature = "tcp_deferaccept"),
38    feature(tcp_deferaccept)
39)]
40#![cfg_attr(
41    all(target_os = "linux", feature = "unix_set_mark"),
42    feature(unix_set_mark)
43)]
44#![cfg_attr(
45    all(target_os = "linux", feature = "unix_socket_ancillary_data"),
46    feature(unix_socket_ancillary_data)
47)]
48//
49// Unix-specific unstable features
50#![cfg_attr(
51    all(unix, feature = "peer_credentials_unix_socket"),
52    feature(peer_credentials_unix_socket)
53)]
54#![cfg_attr(all(unix, feature = "stdio_swap"), feature(stdio_swap))]
55#![cfg_attr(
56    all(unix, feature = "unix_file_vectored_at"),
57    feature(unix_file_vectored_at)
58)]
59#![cfg_attr(all(unix, feature = "unix_mkfifo"), feature(unix_mkfifo))]
60#![cfg_attr(all(unix, feature = "unix_send_signal"), feature(unix_send_signal))]
61#![cfg_attr(all(unix, feature = "unix_socket_peek"), feature(unix_socket_peek))]
62//
63// Windows-specific unstable features
64#![cfg_attr(all(windows, feature = "junction_point"), feature(junction_point))]
65#![cfg_attr(
66    all(windows, feature = "windows_by_handle"),
67    feature(windows_by_handle)
68)]
69#![cfg_attr(
70    all(windows, feature = "windows_change_time"),
71    feature(windows_change_time)
72)]
73#![cfg_attr(
74    all(windows, feature = "windows_process_extensions_raw_attribute"),
75    feature(windows_process_extensions_raw_attribute)
76)]
77#![cfg_attr(
78    all(windows, feature = "windows_unix_domain_sockets"),
79    feature(windows_unix_domain_sockets)
80)]
81//
82// WASI-specific unstable features
83#![cfg_attr(all(target_os = "wasi", feature = "wasi_ext"), feature(wasi_ext))]
84
85use ::std::{any::type_name, fmt::Debug, path::Path, process::Command};
86
87#[allow(unused_parens)]
88#[expect(
89    deprecated,
90    clippy::doc_lazy_continuation,
91    clippy::doc_markdown,
92    clippy::module_name_repetitions
93)]
94#[cfg_attr(dylint_lib = "supplementary", expect(escaping_doc_link))]
95pub mod std;
96
97/// Creates a Cargo command to identify functions that could be replaced with wrapped ones.
98///
99/// The function returns a [`Command`] configured as follows:
100/// - It runs Clippy's [`disallowed_methods` lint] with a [Clippy configuration] (`clippy.toml`)
101///   from this repository.
102/// - `RUSTFLAGS` is set to `--deny=clippy::disallowed-methods`.
103///
104/// # Example
105///
106/// ```no_run
107/// # use elaborate::std::process::CommandContext;
108/// let status = elaborate::disallowed_methods()
109///     .current_dir("/path/to/project")
110///     .arg("--all-targets")
111///     .status_wc()
112///     .unwrap();
113/// assert!(status.success());
114/// ```
115///
116/// [Clippy configuration]: https://doc.rust-lang.org/clippy/configuration.html
117/// [`disallowed_methods` lint]: (https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods)
118#[cfg_attr(dylint_lib = "supplementary", allow(abs_home_path))]
119#[must_use]
120pub fn disallowed_methods() -> Command {
121    let mut command = Command::new("cargo");
122    command.args(["clippy", "--no-deps", "--quiet"]);
123    command.env(
124        "CLIPPY_CONF_DIR",
125        Path::new(env!("CARGO_MANIFEST_DIR")).join("clippy_conf"),
126    );
127    command.env("RUSTFLAGS", "--deny=clippy::disallowed-methods");
128    command
129}
130
131#[macro_export]
132macro_rules! rewrite_output_type {
133    ( $_0:ident $(:: $_1:ident)* < $ty:ty $(, $_2:ty)? $(,)? > ) => {
134        anyhow::Result< $ty >
135    };
136}
137
138#[macro_export]
139macro_rules! call_failed {
140    ($this:expr, $name:literal $(,)?) => {{
141        let mut s = $crate::__call_failed_common!($this, $name);
142        s.push(')');
143        s
144    }};
145    ($this:expr, $name:literal, $($args:expr),+ $(,)?) => {{
146        let mut s = $crate::__call_failed_common!($this, $name);
147        $crate::__call_failed_args!(s, $($args),*);
148        s.push_str("\n    )");
149        s
150    }};
151}
152
153/// Pushes:
154/// - "call failed:"
155/// - newline
156/// - one of:
157///     - indented `self` argument and period (`.`)
158///     - indentation
159/// - function name
160/// - left paren (`(`)
161#[macro_export]
162macro_rules! __call_failed_common {
163    ($this:expr, $name:literal) => {{
164        #[allow(unused_imports)]
165        use $crate::MaybeDebugFallback;
166        let mut s = String::from("call failed:\n");
167        if let Some(this) = $this {
168            s.push_str(&$crate::indent(
169                4,
170                &$crate::MaybeDebug(this).to_debug_string(),
171            ));
172            s.push('.');
173        } else {
174            s.push_str("    ");
175        }
176        s.push_str($name);
177        s.push('(');
178        s
179    }};
180}
181
182#[macro_export]
183macro_rules! __call_failed_args {
184    // Base case:
185    ($s:expr, $arg:expr) => {
186        $crate::__call_failed_args_common!($s, $arg);
187    };
188    // Inductive case:
189    ($s:expr, $arg:expr, $($args:expr),*) => {
190        $crate::__call_failed_args_common!($s, $arg);
191        $crate::__call_failed_args!($s, $($args),*);
192    };
193}
194
195/// Pushes newline, argument, and trailing comma.
196#[macro_export]
197macro_rules! __call_failed_args_common {
198    ($s:expr, $arg:expr) => {{
199        #[allow(unused_imports)]
200        use $crate::MaybeDebugFallback;
201        $s.push('\n');
202        $s.push_str(&$crate::indent(
203            8,
204            &$crate::MaybeDebug($arg).to_debug_string(),
205        ));
206        $s.push(',');
207    }};
208}
209
210fn indent(width: usize, s: &str) -> String {
211    const INDENTATION: &str = "        ";
212    assert!(width <= INDENTATION.len());
213    let mut buf = String::new();
214    for line in s.split_inclusive('\n') {
215        buf.push_str(&INDENTATION[..width]);
216        buf.push_str(line);
217    }
218    buf
219}
220
221// smoelius: `MaybeDebug` uses Nikolai Vazquez's trick from `impls`.
222// https://github.com/nvzqz/impls#how-it-works
223
224pub struct MaybeDebug<T>(pub T);
225
226impl<T> MaybeDebug<T> {
227    pub fn new(value: T) -> Self {
228        Self(value)
229    }
230}
231
232impl<T> MaybeDebug<T>
233where
234    T: Debug,
235{
236    /// If `expr: MaybeDebug<T>` and `T: Debug`, then `expr.to_debug_string()` resolves to this
237    /// inherent method.
238    pub fn to_debug_string(&self) -> String {
239        format!("{:#?}", self.0)
240    }
241}
242
243pub trait MaybeDebugFallback {
244    /// If `expr: MaybeDebug<T>` but not `T: Debug`, then `expr.to_debug_string()` resolves to this
245    /// trait method.
246    fn to_debug_string(&self) -> String;
247}
248
249impl<T> MaybeDebugFallback for T {
250    fn to_debug_string(&self) -> String {
251        const PAT: &str = "MaybeDebug<";
252        let type_name = type_name::<T>();
253        let pos = type_name.find(PAT).unwrap() + PAT.len();
254        let generic_arg = type_name[pos..].strip_suffix('>').unwrap();
255        format!("<value of type {generic_arg}>")
256    }
257}
258
259struct CustomDebugMessage(&'static str);
260
261impl Debug for CustomDebugMessage {
262    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
263        let msg = self.0;
264        write!(f, "<{msg}>")
265    }
266}