[RFC 2011] Minimal initial implementation · rust-lang/rust@605c64a (original) (raw)
1
``
`-
use rustc_ast::{ptr::P, Expr, Path};
`
``
1
`+
use crate::assert::expr_if_not;
`
``
2
`+
use rustc_ast::{
`
``
3
`+
attr,
`
``
4
`+
ptr::P,
`
``
5
`+
token,
`
``
6
`+
tokenstream::{DelimSpan, TokenStream, TokenTree},
`
``
7
`+
BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability, Path,
`
``
8
`+
PathSegment, Stmt, UseTree, UseTreeKind, DUMMY_NODE_ID,
`
``
9
`+
};
`
``
10
`+
use rustc_ast_pretty::pprust;
`
``
11
`+
use rustc_data_structures::fx::FxHashSet;
`
2
12
`use rustc_expand::base::ExtCtxt;
`
3
``
`-
use rustc_span::Span;
`
``
13
`+
use rustc_span::{
`
``
14
`+
symbol::{sym, Ident, Symbol},
`
``
15
`+
Span,
`
``
16
`+
};
`
4
17
``
5
18
`pub(super) struct Context<'cx, 'a> {
`
``
19
`` +
// Top-level let captureN = Capture::new()
statements
``
``
20
`+
capture_decls: Vec,
`
6
21
`cx: &'cx ExtCtxt<'a>,
`
``
22
`+
// Formatting string used for debugging
`
``
23
`+
fmt_string: String,
`
``
24
`` +
// Top-level let __local_bindN = &expr
statements
``
``
25
`+
local_bind_decls: Vec,
`
``
26
`+
// Used to avoid capturing duplicated paths
`
``
27
`+
//
`
``
28
// ```rust
``
29
`+
// let a = 1i32;
`
``
30
`+
// assert!(add(a, a) == 3);
`
``
31
// ```
``
32
`+
paths: FxHashSet,
`
7
33
`span: Span,
`
8
34
`}
`
9
35
``
10
36
`impl<'cx, 'a> Context<'cx, 'a> {
`
11
37
`pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
`
12
``
`-
Self { cx, span }
`
``
38
`+
Self {
`
``
39
`+
capture_decls: <_>::default(),
`
``
40
`+
cx,
`
``
41
`+
fmt_string: <_>::default(),
`
``
42
`+
local_bind_decls: <_>::default(),
`
``
43
`+
paths: <_>::default(),
`
``
44
`+
span,
`
``
45
`+
}
`
13
46
`}
`
14
47
``
15
``
`` -
/// Builds the whole assert!
expression.
``
``
48
`` +
/// Builds the whole assert!
expression. For example, let elem = 1; assert!(elem == 1);
expands to:
``
16
49
`///
`
``
50
/// ```rust
``
51
`+
/// let elem = 1;
`
17
52
`/// {
`
18
``
`-
/// use ::core::asserting::{ ... };
`
``
53
`+
/// #[allow(unused_imports)]
`
``
54
`+
/// use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
`
``
55
`+
/// let mut __capture0 = ::core::asserting::Capture::new();
`
``
56
`+
/// let __local_bind0 = &elem;
`
``
57
`+
/// if !(
`
``
58
`+
/// *{
`
``
59
`+
/// (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
`
``
60
`+
/// __local_bind0
`
``
61
`+
/// } == 1
`
``
62
`+
/// ) {
`
``
63
`+
/// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {}", __capture0)
`
``
64
`+
/// }
`
``
65
`+
/// }
`
``
66
/// ```
``
67
`+
pub(super) fn build(mut self, mut cond_expr: P, panic_path: Path) -> P {
`
``
68
`+
let expr_str = pprust::expr_to_string(&cond_expr);
`
``
69
`+
self.manage_cond_expr(&mut cond_expr);
`
``
70
`+
let initial_imports = self.build_initial_imports();
`
``
71
`+
let panic = self.build_panic(&expr_str, panic_path);
`
``
72
+
``
73
`+
let Self { capture_decls, cx, local_bind_decls, span, .. } = self;
`
``
74
+
``
75
`+
let mut stmts = Vec::with_capacity(4);
`
``
76
`+
stmts.push(initial_imports);
`
``
77
`+
stmts.extend(capture_decls.into_iter().map(|c| c.decl));
`
``
78
`+
stmts.extend(local_bind_decls);
`
``
79
`+
stmts.push(cx.stmt_expr(expr_if_not(cx, span, cond_expr, panic, None)));
`
``
80
`+
cx.expr_block(cx.block(span, stmts))
`
``
81
`+
}
`
``
82
+
``
83
`+
/// Initial trait imports
`
``
84
`+
///
`
``
85
`+
/// use ::core::asserting::{ ... };
`
``
86
`+
fn build_initial_imports(&self) -> Stmt {
`
``
87
`+
let nested_tree = |this: &Self, sym| {
`
``
88
`+
(
`
``
89
`+
UseTree {
`
``
90
`+
prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]),
`
``
91
`+
kind: UseTreeKind::Simple(None, DUMMY_NODE_ID, DUMMY_NODE_ID),
`
``
92
`+
span: this.span,
`
``
93
`+
},
`
``
94
`+
DUMMY_NODE_ID,
`
``
95
`+
)
`
``
96
`+
};
`
``
97
`+
self.cx.stmt_item(
`
``
98
`+
self.span,
`
``
99
`+
self.cx.item(
`
``
100
`+
self.span,
`
``
101
`+
Ident::empty(),
`
``
102
`+
vec![self.cx.attribute(attr::mk_list_item(
`
``
103
`+
Ident::new(sym::allow, self.span),
`
``
104
`+
vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))],
`
``
105
`+
))],
`
``
106
`+
ItemKind::Use(UseTree {
`
``
107
`+
prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
`
``
108
`+
kind: UseTreeKind::Nested(vec![
`
``
109
`+
nested_tree(self, sym::TryCaptureGeneric),
`
``
110
`+
nested_tree(self, sym::TryCapturePrintable),
`
``
111
`+
]),
`
``
112
`+
span: self.span,
`
``
113
`+
}),
`
``
114
`+
),
`
``
115
`+
)
`
``
116
`+
}
`
``
117
+
``
118
`` +
/// The necessary custom panic!(...)
expression.
``
``
119
`+
///
`
``
120
`+
/// panic!(
`
``
121
`+
/// "Assertion failed: ... \n With expansion: ...",
`
``
122
`+
/// __capture0,
`
``
123
`+
/// ...
`
``
124
`+
/// );
`
``
125
`+
fn build_panic(&self, expr_str: &str, panic_path: Path) -> P {
`
``
126
`+
let escaped_expr_str = escape_to_fmt(expr_str);
`
``
127
`+
let initial = [
`
``
128
`+
TokenTree::token(
`
``
129
`+
token::Literal(token::Lit {
`
``
130
`+
kind: token::LitKind::Str,
`
``
131
`+
symbol: Symbol::intern(&if self.fmt_string.is_empty() {
`
``
132
`+
format!("Assertion failed: {escaped_expr_str}")
`
``
133
`+
} else {
`
``
134
`+
format!(
`
``
135
`+
"Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
`
``
136
`+
&self.fmt_string
`
``
137
`+
)
`
``
138
`+
}),
`
``
139
`+
suffix: None,
`
``
140
`+
}),
`
``
141
`+
self.span,
`
``
142
`+
),
`
``
143
`+
TokenTree::token(token::Comma, self.span),
`
``
144
`+
];
`
``
145
`+
let captures = self.capture_decls.iter().flat_map(|cap| {
`
``
146
`+
[
`
``
147
`+
TokenTree::token(token::Ident(cap.ident.name, false), cap.ident.span),
`
``
148
`+
TokenTree::token(token::Comma, self.span),
`
``
149
`+
]
`
``
150
`+
});
`
``
151
`+
self.cx.expr(
`
``
152
`+
self.span,
`
``
153
`+
ExprKind::MacCall(MacCall {
`
``
154
`+
path: panic_path,
`
``
155
`+
args: P(MacArgs::Delimited(
`
``
156
`+
DelimSpan::from_single(self.span),
`
``
157
`+
MacDelimiter::Parenthesis,
`
``
158
`+
initial.into_iter().chain(captures).collect::(),
`
``
159
`+
)),
`
``
160
`+
prior_type_ascription: None,
`
``
161
`+
}),
`
``
162
`+
)
`
``
163
`+
}
`
``
164
+
``
165
`` +
/// Recursive function called until cond_expr
and fmt_str
are fully modified.
``
``
166
`+
///
`
``
167
`+
/// See [Self::manage_initial_capture] and [Self::manage_try_capture]
`
``
168
`+
fn manage_cond_expr(&mut self, expr: &mut P) {
`
``
169
`+
match (*expr).kind {
`
``
170
`+
ExprKind::Binary(_, ref mut lhs, ref mut rhs) => {
`
``
171
`+
self.manage_cond_expr(lhs);
`
``
172
`+
self.manage_cond_expr(rhs);
`
``
173
`+
}
`
``
174
`+
ExprKind::Path(_, Path { ref segments, .. }) if let &[ref path_segment] = &segments[..] => {
`
``
175
`+
let path_ident = path_segment.ident;
`
``
176
`+
self.manage_initial_capture(expr, path_ident);
`
``
177
`+
}
`
``
178
`+
_ => {}
`
``
179
`+
}
`
``
180
`+
}
`
``
181
+
``
182
`` +
/// Pushes the top-level declarations and modifies expr
to try capturing variables.
``
19
183
`///
`
20
``
`-
/// let mut __capture0 = Capture::new();
`
21
``
`-
/// ...
`
22
``
`-
/// ...
`
23
``
`-
/// ...
`
``
184
`` +
/// fmt_str
, the formatting string used for debugging, is constructed to show possible
``
``
185
`+
/// captured variables.
`
``
186
`+
fn manage_initial_capture(&mut self, expr: &mut P, path_ident: Ident) {
`
``
187
`+
if self.paths.contains(&path_ident) {
`
``
188
`+
return;
`
``
189
`+
} else {
`
``
190
`+
self.fmt_string.push_str(" ");
`
``
191
`+
self.fmt_string.push_str(path_ident.as_str());
`
``
192
`+
self.fmt_string.push_str(" = {:?}\n");
`
``
193
`+
let _ = self.paths.insert(path_ident);
`
``
194
`+
}
`
``
195
`+
let curr_capture_idx = self.capture_decls.len();
`
``
196
`+
let capture_string = format!("__capture{curr_capture_idx}");
`
``
197
`+
let ident = Ident::new(Symbol::intern(&capture_string), self.span);
`
``
198
`+
let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
`
``
199
`+
let init = self.cx.expr_call(
`
``
200
`+
self.span,
`
``
201
`+
self.cx.expr_path(self.cx.path(self.span, init_std_path)),
`
``
202
`+
vec![],
`
``
203
`+
);
`
``
204
`+
let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
`
``
205
`+
self.capture_decls.push(capture);
`
``
206
`+
self.manage_try_capture(ident, curr_capture_idx, expr);
`
``
207
`+
}
`
``
208
+
``
209
`` +
/// Tries to copy __local_bindN
into __captureN
.
``
24
210
`///
`
25
``
`-
/// if !{
`
26
``
`-
/// ...
`
27
``
`-
/// ...
`
28
``
`-
/// ...
`
29
``
`-
/// } {
`
30
``
`-
/// panic!(
`
31
``
`-
/// "Assertion failed: ... \n With expansion: ...",
`
32
``
`-
/// __capture0,
`
33
``
`-
/// ...
`
34
``
`-
/// ...
`
35
``
`-
/// ...
`
36
``
`-
/// );
`
37
``
`-
/// }
`
``
211
`+
/// *{
`
``
212
`+
/// (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
`
``
213
`+
/// __local_bindN
`
38
214
`/// }
`
39
``
`-
pub(super) fn build(self, _cond_expr: P, _panic_path: Path) -> P {
`
40
``
`-
let Self { cx, span, .. } = self;
`
41
``
`-
let stmts = Vec::new();
`
42
``
`-
cx.expr_block(cx.block(span, stmts))
`
``
215
`+
fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P) {
`
``
216
`+
let local_bind_string = format!("__local_bind{curr_capture_idx}");
`
``
217
`+
let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
`
``
218
`+
self.local_bind_decls.push(self.cx.stmt_let(
`
``
219
`+
self.span,
`
``
220
`+
false,
`
``
221
`+
local_bind,
`
``
222
`+
self.cx.expr_addr_of(self.span, expr.clone()),
`
``
223
`+
));
`
``
224
`+
let wrapper = self.cx.expr_call(
`
``
225
`+
self.span,
`
``
226
`+
self.cx.expr_path(
`
``
227
`+
self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
`
``
228
`+
),
`
``
229
`+
vec![self.cx.expr_path(Path::from_ident(local_bind))],
`
``
230
`+
);
`
``
231
`+
let try_capture_call = self
`
``
232
`+
.cx
`
``
233
`+
.stmt_expr(expr_method_call(
`
``
234
`+
self.cx,
`
``
235
`+
PathSegment {
`
``
236
`+
args: None,
`
``
237
`+
id: DUMMY_NODE_ID,
`
``
238
`+
ident: Ident::new(sym::try_capture, self.span),
`
``
239
`+
},
`
``
240
`+
vec![
`
``
241
`+
expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
`
``
242
`+
expr_addr_of_mut(
`
``
243
`+
self.cx,
`
``
244
`+
self.span,
`
``
245
`+
self.cx.expr_path(Path::from_ident(capture)),
`
``
246
`+
),
`
``
247
`+
],
`
``
248
`+
self.span,
`
``
249
`+
))
`
``
250
`+
.add_trailing_semicolon();
`
``
251
`+
let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
`
``
252
`+
let ret = self.cx.stmt_expr(local_bind_path);
`
``
253
`+
let block = self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret]));
`
``
254
`+
*expr = self.cx.expr_deref(self.span, block);
`
``
255
`+
}
`
``
256
`+
}
`
``
257
+
``
258
`+
/// Information about a captured element.
`
``
259
`+
#[derive(Debug)]
`
``
260
`+
struct Capture {
`
``
261
`` +
// Generated indexed Capture
statement.
``
``
262
`+
//
`
``
263
`` +
// let __capture{} = Capture::new();
``
``
264
`+
decl: Stmt,
`
``
265
`` +
// The name of the generated indexed Capture
variable.
``
``
266
`+
//
`
``
267
`` +
// __capture{}
``
``
268
`+
ident: Ident,
`
``
269
`+
}
`
``
270
+
``
271
`+
/// Escapes to use as a formatting string.
`
``
272
`+
fn escape_to_fmt(s: &str) -> String {
`
``
273
`+
let mut rslt = String::with_capacity(s.len());
`
``
274
`+
for c in s.chars() {
`
``
275
`+
rslt.extend(c.escape_debug());
`
``
276
`+
match c {
`
``
277
`+
'{' | '}' => rslt.push(c),
`
``
278
`+
_ => {}
`
``
279
`+
}
`
43
280
`}
`
``
281
`+
rslt
`
``
282
`+
}
`
``
283
+
``
284
`+
fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P {
`
``
285
`+
cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
`
``
286
`+
}
`
``
287
+
``
288
`+
fn expr_method_call(
`
``
289
`+
cx: &ExtCtxt<'_>,
`
``
290
`+
path: PathSegment,
`
``
291
`+
args: Vec<P>,
`
``
292
`+
span: Span,
`
``
293
`+
) -> P {
`
``
294
`+
cx.expr(span, ExprKind::MethodCall(path, args, span))
`
``
295
`+
}
`
``
296
+
``
297
`+
fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P {
`
``
298
`+
cx.expr(sp, ExprKind::Paren(e))
`
44
299
`}
`