hodoku/lib.rs
1//! [<img alt="github" src="https://img.shields.io/badge/github-udoprog/hodoku-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/hodoku)
2//! [<img alt="crates.io" src="https://img.shields.io/crates/v/hodoku.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/hodoku)
3//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-hodoku-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/hodoku)
4//!
5//! A simple set of macros to aid testing with try operations.
6//!
7//! This crate allows for easily writing functions and expression where `?` is
8//! automatically translated into `.unwrap()`.
9//!
10//! It is syntactically desirable to use `?`. This however causes issues during
11//! testing, because a failing test lacks a stack trace which helps you track
12//! down the exact line that errored.
13//!
14//! ```
15//! # fn function() -> Result<u32, &'static str> { Ok(42) };
16//! #[test]
17//! fn test_case() -> Result<(), &'static str> {
18//! let value = function()?;
19//! assert_eq!(value, 42);
20//! Ok(())
21//! }
22//! ```
23//!
24//! By default you'd get this when `function()?` errors:
25//!
26//! ```text
27//! ---- test_case stdout ----
28//! Error: "bad"
29//! thread 'test_case' panicked at 'assertion failed: `(left == right)`
30//! left: `1`,
31//! right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', <path>\library\test\src\lib.rs:185:5
32//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
33//!
34//!
35//! failures:
36//! test_case
37//! ```
38//!
39//! Note how there's no information on which line the test failed on.
40//!
41//! But with the inclusion of `#[hodoku::function]` you get this:
42//!
43//! ```
44//! # fn function() -> Result<u32, &'static str> { Err("bad") };
45//! #[test]
46//! #[hodoku::function]
47//! fn test_case() -> Result<(), &'static str> {
48//! let value = function()?;
49//! assert_eq!(value, 42);
50//! Ok(())
51//! }
52//! ```
53//!
54//! ```text
55//! ---- test_case stdout ----
56//! thread 'test_case' panicked at 'called `Result::unwrap()` on an `Err` value: "bad"', tests\failing.rs:8:27
57//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
58//!
59//!
60//! failures:
61//! test_case
62//! ```
63//!
64//! This is exactly why we want to make use of `.unwrap()` instead of the try
65//! operator tests. It indicates the exact line that errored.
66//!
67//! <br>
68//!
69//! ## Examples
70//!
71//! Use of `#[hodoku::function]`.
72//!
73//! ```
74//! #[hodoku::function]
75//! fn hello() {
76//! let value = Some(42)?;
77//! assert_eq!(value, 42);
78//! }
79//!
80//! hello();
81//! ```
82//!
83//! Unwrapping expressions:
84//!
85//! ```
86//! let value = hodoku::expr!(Some(42)?);
87//! assert_eq!(value, 42);
88//! ```
89
90use std::array;
91use std::iter;
92
93use proc_macro::Spacing;
94use proc_macro::{Delimiter, Group, Ident, Punct, TokenStream, TokenTree};
95
96/// Process an expression or item marked with an attribute to modify any uses of
97/// the try operator `?` into trailing `.unwrap()`. So `Some(42)?` will be
98/// translated to `Some(42).unwrap()`.
99///
100/// This is useful for adhoc testing.
101///
102/// # Examples
103///
104/// ```
105/// #[hodoku::function]
106/// fn hello() {
107/// let value = Some(42)?;
108/// assert_eq!(value, 42);
109/// }
110///
111/// hello();
112/// ```
113#[proc_macro_attribute]
114pub fn function(args: TokenStream, item: TokenStream) -> TokenStream {
115 if let Some(..) = args.into_iter().next() {
116 panic!("#[hodoku::function]: takes not arguments")
117 }
118
119 process(item)
120}
121
122/// Process an expression to modify any uses of the try operator `?` into
123/// trailing `.unwrap()`. So `expr!(Some(42)?)` will be translated to
124/// `Some(42).unwrap()`.
125///
126/// This is useful for adhoc testing.
127///
128/// # Examples
129///
130/// ```
131/// let value = hodoku::expr!(Some(42)?);
132/// assert_eq!(value, 42);
133/// ```
134#[proc_macro]
135pub fn expr(input: TokenStream) -> TokenStream {
136 process(input)
137}
138
139fn process(item: TokenStream) -> TokenStream {
140 let mut it = item.into_iter();
141 let mut tmp = None::<array::IntoIter<TokenTree, 2>>;
142
143 TokenStream::from_iter(iter::from_fn(move || {
144 if let Some(buf) = tmp.as_mut() {
145 if let Some(tt) = buf.next() {
146 return Some(tt);
147 }
148
149 tmp = None;
150 }
151
152 match it.next()? {
153 TokenTree::Group(g) => Some(TokenTree::Group(Group::new(
154 g.delimiter(),
155 process(g.stream()),
156 ))),
157 TokenTree::Punct(punct) => {
158 if punct.as_char() == '?' {
159 let mut group = Group::new(Delimiter::Parenthesis, TokenStream::default());
160 group.set_span(punct.span());
161
162 tmp = Some(
163 [
164 TokenTree::Ident(Ident::new("unwrap", punct.span())),
165 TokenTree::Group(group),
166 ]
167 .into_iter(),
168 );
169
170 let mut first = Punct::new('.', Spacing::Joint);
171 first.set_span(punct.span());
172 Some(TokenTree::Punct(first))
173 } else {
174 Some(TokenTree::Punct(punct))
175 }
176 }
177 tt => Some(tt),
178 }
179 }))
180}