1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use std::fmt::Display;
use super::*;
struct Variable {
/// Name of the variable
name: String,
/// Debug-printed value
debug_value: TokenStream,
/// Setup code to create the variable
setup: TokenStream,
/// Flag if the variable might be moved by the condition
might_move: bool,
}
pub struct Variables {
/// Pairs of (variable name, debug-printed value) that are used in the assertion and should be printed in the error message
variables: Vec<Variable>,
/// Counter for creating unique identifiers
next_ident_id: usize,
}
impl Variables {
pub fn new() -> Self {
Self {
variables: vec![],
next_ident_id: 0,
}
}
/// Ensure that there is no conflict between identifiers in the generated code by adding an incrementing number to each identifier
fn create_ident(&mut self, name: impl Display) -> syn::Ident {
let name = format!("__one_assert_{}_{}", name, self.next_ident_id);
self.next_ident_id += 1;
syn::Ident::new(&name, Span::call_site())
}
/// Create a variable from an expression and store it in the setup code.
///
/// It is assumed that the expression moves the value, so it needs to be debug printed in advance
pub fn add_moving_var(
&mut self,
expr: impl Borrow<syn::Expr>,
identifier: impl Display,
display: impl Display,
) -> TokenStream {
self.add_var_internal(expr, identifier, display, true)
}
/// Create a variable from an expression and store it in the setup code.
///
/// The variable is only used in a borrowed form, so we can debug print only when needed
pub fn add_borrowed_var(
&mut self,
expr: impl Borrow<syn::Expr>,
identifier: impl Display,
display: impl Display,
) -> TokenStream {
self.add_var_internal(expr, identifier, display, false)
}
fn add_var_internal(
&mut self,
expr: impl Borrow<syn::Expr>,
identifier: impl Display,
display: impl Display,
might_move: bool,
) -> TokenStream {
let expr = expr.borrow();
let mut setup = TokenStream::new();
let var_access = if matches!(expr, syn::Expr::Path(_)) {
// could be a variable of a type that doesn't implement Copy, so we can't store it by value.
// Instead, we just use the variable directly.
expr.to_token_stream()
} else {
// any other expression. Compute the result once and store it
let var_ident = self.create_ident(&identifier);
setup.extend(quote! {
let #var_ident = __OneAssertWrapper(#expr);
});
// See note at the end of the file for an explanation on the span manipulation here
let expr_span = FullSpan::from_spanned(&expr);
expr_span.apply(quote! { #var_ident }, quote! { .0 })
};
let debug_value = if might_move {
let var_debug_str = self.create_ident(format_args!("{identifier}_str"));
setup.extend(quote! {
let #var_debug_str = ::std::format!("{:?}", #var_access);
});
var_debug_str.to_token_stream()
} else {
var_access.clone()
};
// store variable for now instead of printing it immediately, so that all the variables can be aligned
self.variables.push(Variable {
name: display.to_string(),
debug_value,
setup,
might_move,
});
var_access
}
/// Add a `Name: Value` block for all currently stored variables to the format message
pub fn resolve_variables(
&mut self,
setup: &mut TokenStream,
format_message: &mut FormatMessage,
) {
let Some(max_name_len) = self
.variables
.iter()
.map(|Variable { name, .. }| name.len())
.max()
else {
return; // no variables to print
};
for Variable {
name,
debug_value,
setup: var_setup,
might_move,
} in self.variables.drain(..)
{
setup.extend(var_setup);
format_message.add_text(format_args!("\n {name:>max_name_len$}: "));
if might_move {
// value was already debug-printed ahead of time => just print the string normally
format_message.add_placeholder("{}", debug_value);
} else {
format_message.add_placeholder("{:?}", debug_value);
}
}
}
}