Skip to content

Commit adaefdc

Browse files
pnkfelixcelinval
authored andcommitted
attribute-based contract syntax that desugars into the internal AST extensions added earlier.
1 parent 38eff16 commit adaefdc

File tree

10 files changed

+381
-65
lines changed

10 files changed

+381
-65
lines changed

compiler/rustc_ast_lowering/src/item.rs

Lines changed: 55 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
215215
if let Some(contract) = contract {
216216
let requires = contract.requires.clone();
217217
let ensures = contract.ensures.clone();
218-
let ensures = if let Some(ens) = ensures {
218+
let ensures = ensures.map(|ens| {
219219
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
220220
// accidental capture of a parameter or global variable.
221221
let checker_ident: Ident =
@@ -226,13 +226,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
226226
hir::BindingMode::NONE,
227227
);
228228

229-
Some(crate::FnContractLoweringEnsures {
229+
crate::FnContractLoweringEnsures {
230230
expr: ens,
231231
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
232-
})
233-
} else {
234-
None
235-
};
232+
}
233+
});
236234

237235
// Note: `with_new_scopes` will reinstall the outer
238236
// item's contract (if any) after its callback finishes.
@@ -1096,72 +1094,64 @@ impl<'hir> LoweringContext<'_, 'hir> {
10961094
// { body }
10971095
// ==>
10981096
// { rustc_contract_requires(PRECOND); { body } }
1099-
let result: hir::Expr<'hir> = if let Some(contract) = opt_contract {
1100-
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
1101-
this.expr(contract.span, hir::ExprKind::Tup(&[]))
1102-
};
1097+
let Some(contract) = opt_contract else { return (params, result) };
11031098

1104-
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
1105-
let lowered_req = this.lower_expr_mut(&req);
1106-
let precond = this.expr_call_lang_item_fn_mut(
1107-
req.span,
1108-
hir::LangItem::ContractCheckRequires,
1109-
&*arena_vec![this; lowered_req],
1110-
);
1111-
this.stmt_expr(req.span, precond)
1112-
} else {
1113-
let u = lit_unit(this);
1114-
this.stmt_expr(contract.span, u)
1115-
};
1099+
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
1100+
this.expr(contract.span, hir::ExprKind::Tup(&[]))
1101+
};
11161102

1117-
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
1118-
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
1119-
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
1120-
let postcond_checker = this.expr_call_lang_item_fn(
1121-
ens.span,
1122-
hir::LangItem::ContractBuildCheckEnsures,
1123-
&*arena_vec![this; lowered_ens],
1124-
);
1125-
let checker_binding_pat = fresh_ident.1;
1126-
(
1127-
this.stmt_let_pat(
1128-
None,
1129-
ens.span,
1130-
Some(postcond_checker),
1131-
this.arena.alloc(checker_binding_pat),
1132-
hir::LocalSource::Contract,
1133-
),
1134-
{
1135-
let checker_fn =
1136-
this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
1137-
let span = this.mark_span_with_reason(
1138-
DesugaringKind::Contract,
1139-
ens.span,
1140-
None,
1141-
);
1142-
this.expr_call_mut(
1143-
span,
1144-
checker_fn,
1145-
std::slice::from_ref(this.arena.alloc(result)),
1146-
)
1147-
},
1148-
)
1149-
} else {
1150-
let u = lit_unit(this);
1151-
(this.stmt_expr(contract.span, u), result)
1152-
};
1103+
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
1104+
let lowered_req = this.lower_expr_mut(&req);
1105+
let precond = this.expr_call_lang_item_fn_mut(
1106+
req.span,
1107+
hir::LangItem::ContractCheckRequires,
1108+
&*arena_vec![this; lowered_req],
1109+
);
1110+
this.stmt_expr(req.span, precond)
1111+
} else {
1112+
let u = lit_unit(this);
1113+
this.stmt_expr(contract.span, u)
1114+
};
11531115

1154-
let block = this.block_all(
1155-
contract.span,
1156-
arena_vec![this; precond, postcond_checker],
1157-
Some(this.arena.alloc(result)),
1116+
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
1117+
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
1118+
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
1119+
let postcond_checker = this.expr_call_lang_item_fn(
1120+
ens.span,
1121+
hir::LangItem::ContractBuildCheckEnsures,
1122+
&*arena_vec![this; lowered_ens],
11581123
);
1159-
this.expr_block(block)
1124+
let checker_binding_pat = fresh_ident.1;
1125+
(
1126+
this.stmt_let_pat(
1127+
None,
1128+
ens.span,
1129+
Some(postcond_checker),
1130+
this.arena.alloc(checker_binding_pat),
1131+
hir::LocalSource::Contract,
1132+
),
1133+
{
1134+
let checker_fn = this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
1135+
let span =
1136+
this.mark_span_with_reason(DesugaringKind::Contract, ens.span, None);
1137+
this.expr_call_mut(
1138+
span,
1139+
checker_fn,
1140+
std::slice::from_ref(this.arena.alloc(result)),
1141+
)
1142+
},
1143+
)
11601144
} else {
1161-
result
1145+
let u = lit_unit(this);
1146+
(this.stmt_expr(contract.span, u), result)
11621147
};
11631148

1164-
(params, result)
1149+
let block = this.block_all(
1150+
contract.span,
1151+
arena_vec![this; precond, postcond_checker],
1152+
Some(this.arena.alloc(result)),
1153+
);
1154+
(params, this.expr_block(block))
11651155
})
11661156
}
11671157

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#![allow(unused_imports, unused_variables)]
2+
3+
use rustc_ast::token;
4+
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
5+
use rustc_errors::ErrorGuaranteed;
6+
use rustc_expand::base::{AttrProcMacro, ExtCtxt};
7+
use rustc_span::Span;
8+
use rustc_span::symbol::{Ident, Symbol, kw, sym};
9+
10+
pub(crate) struct ExpandRequires;
11+
12+
pub(crate) struct ExpandEnsures;
13+
14+
impl AttrProcMacro for ExpandRequires {
15+
fn expand<'cx>(
16+
&self,
17+
ecx: &'cx mut ExtCtxt<'_>,
18+
span: Span,
19+
annotation: TokenStream,
20+
annotated: TokenStream,
21+
) -> Result<TokenStream, ErrorGuaranteed> {
22+
expand_requires_tts(ecx, span, annotation, annotated)
23+
}
24+
}
25+
26+
impl AttrProcMacro for ExpandEnsures {
27+
fn expand<'cx>(
28+
&self,
29+
ecx: &'cx mut ExtCtxt<'_>,
30+
span: Span,
31+
annotation: TokenStream,
32+
annotated: TokenStream,
33+
) -> Result<TokenStream, ErrorGuaranteed> {
34+
expand_ensures_tts(ecx, span, annotation, annotated)
35+
}
36+
}
37+
38+
fn expand_injecting_circa_where_clause(
39+
_ecx: &mut ExtCtxt<'_>,
40+
attr_span: Span,
41+
annotated: TokenStream,
42+
inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>,
43+
) -> Result<TokenStream, ErrorGuaranteed> {
44+
let mut new_tts = Vec::with_capacity(annotated.len());
45+
let mut cursor = annotated.into_trees();
46+
47+
// Find the `fn name<G,...>(x:X,...)` and inject the AST contract forms right after
48+
// the formal parameters (and return type if any).
49+
while let Some(tt) = cursor.next_ref() {
50+
new_tts.push(tt.clone());
51+
if let TokenTree::Token(tok, _) = tt
52+
&& tok.is_ident_named(kw::Fn)
53+
{
54+
break;
55+
}
56+
}
57+
58+
// Found the `fn` keyword, now find the formal parameters.
59+
//
60+
// FIXME: can this fail if you have parentheticals in a generics list, like `fn foo<F: Fn(X) -> Y>` ?
61+
while let Some(tt) = cursor.next_ref() {
62+
new_tts.push(tt.clone());
63+
64+
if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt {
65+
break;
66+
}
67+
if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt {
68+
panic!("contract attribute applied to fn without parameter list.");
69+
}
70+
}
71+
72+
// There *might* be a return type declaration (and figuring out where that ends would require
73+
// parsing an arbitrary type expression, e.g. `-> Foo<args ...>`
74+
//
75+
// Instead of trying to figure that out, scan ahead and look for the first occurence of a
76+
// `where`, a `{ ... }`, or a `;`.
77+
//
78+
// FIXME: this might still fall into a trap for something like `-> Ctor<T, const { 0 }>`. I
79+
// *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form
80+
// prefixed by e.g. `const`, so we should still be able to filter them out without having to
81+
// parse the type expression itself. But rather than try to fix things with hacks like that,
82+
// time might be better spent extending the attribute expander to suport tt-annotation atop
83+
// ast-annotated, which would be an elegant way to sidestep all of this.
84+
let mut opt_next_tt = cursor.next_ref();
85+
while let Some(next_tt) = opt_next_tt {
86+
if let TokenTree::Token(tok, _) = next_tt
87+
&& tok.is_ident_named(kw::Where)
88+
{
89+
break;
90+
}
91+
if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt {
92+
break;
93+
}
94+
if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt {
95+
break;
96+
}
97+
98+
// for anything else, transcribe the tt and keep looking.
99+
new_tts.push(next_tt.clone());
100+
opt_next_tt = cursor.next_ref();
101+
continue;
102+
}
103+
104+
// At this point, we've transcribed everything from the `fn` through the formal parameter list
105+
// and return type declaration, (if any), but `tt` itself has *not* been transcribed.
106+
//
107+
// Now inject the AST contract form.
108+
//
109+
// FIXME: this kind of manual token tree munging does not have significant precedent among
110+
// rustc builtin macros, probably because most builtin macros use direct AST manipulation to
111+
// accomplish similar goals. But since our attributes need to take arbitrary expressions, and
112+
// our attribute infrastructure does not yet support mixing a token-tree annotation with an AST
113+
// annotated, we end up doing token tree manipulation.
114+
inject(&mut new_tts)?;
115+
116+
// Above we injected the internal AST requires/ensures contruct. Now copy over all the other
117+
// token trees.
118+
if let Some(tt) = opt_next_tt {
119+
new_tts.push(tt.clone());
120+
}
121+
while let Some(tt) = cursor.next_ref() {
122+
new_tts.push(tt.clone());
123+
}
124+
125+
Ok(TokenStream::new(new_tts))
126+
}
127+
128+
fn expand_requires_tts(
129+
_ecx: &mut ExtCtxt<'_>,
130+
attr_span: Span,
131+
annotation: TokenStream,
132+
annotated: TokenStream,
133+
) -> Result<TokenStream, ErrorGuaranteed> {
134+
expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| {
135+
new_tts.push(TokenTree::Token(
136+
token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)),
137+
Spacing::Joint,
138+
));
139+
new_tts.push(TokenTree::Token(
140+
token::Token::new(token::TokenKind::OrOr, attr_span),
141+
Spacing::Alone,
142+
));
143+
new_tts.push(TokenTree::Delimited(
144+
DelimSpan::from_single(attr_span),
145+
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
146+
token::Delimiter::Parenthesis,
147+
annotation,
148+
));
149+
Ok(())
150+
})
151+
}
152+
153+
fn expand_ensures_tts(
154+
_ecx: &mut ExtCtxt<'_>,
155+
attr_span: Span,
156+
annotation: TokenStream,
157+
annotated: TokenStream,
158+
) -> Result<TokenStream, ErrorGuaranteed> {
159+
expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| {
160+
new_tts.push(TokenTree::Token(
161+
token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)),
162+
Spacing::Joint,
163+
));
164+
new_tts.push(TokenTree::Delimited(
165+
DelimSpan::from_single(attr_span),
166+
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
167+
token::Delimiter::Parenthesis,
168+
annotation,
169+
));
170+
Ok(())
171+
})
172+
}

compiler/rustc_builtin_macros/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mod trace_macros;
5555

5656
pub mod asm;
5757
pub mod cmdline_attrs;
58+
pub mod contracts;
5859
pub mod proc_macro_harness;
5960
pub mod standard_library_imports;
6061
pub mod test_harness;
@@ -137,4 +138,8 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
137138

138139
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
139140
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
141+
let requires = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandRequires));
142+
register(sym::contracts_requires, requires);
143+
let ensures = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandEnsures));
144+
register(sym::contracts_ensures, ensures);
140145
}

compiler/rustc_span/src/symbol.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,8 @@ symbols! {
682682
contract_check_ensures,
683683
contract_check_requires,
684684
contract_checks,
685+
contracts_ensures,
686+
contracts_requires,
685687
convert_identity,
686688
copy,
687689
copy_closures,

library/core/src/contracts.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
//! Unstable module containing the unstable contracts lang items and attribute macros.
22
3+
#[cfg(not(bootstrap))]
4+
pub use crate::macros::builtin::contracts_ensures as ensures;
5+
#[cfg(not(bootstrap))]
6+
pub use crate::macros::builtin::contracts_requires as requires;
7+
38
/// Emitted by rustc as a desugaring of `#[requires(PRED)] fn foo(x: X) { ... }`
49
/// into: `fn foo(x: X) { check_requires(|| PRED) ... }`
510
#[cfg(not(bootstrap))]

library/core/src/macros/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,6 +1777,32 @@ pub(crate) mod builtin {
17771777
/* compiler built-in */
17781778
}
17791779

1780+
/// Attribute macro applied to a function to give it a post-condition.
1781+
///
1782+
/// The attribute carries an argument token-tree which is
1783+
/// eventually parsed as a unary closure expression that is
1784+
/// invoked on a reference to the return value.
1785+
#[cfg(not(bootstrap))]
1786+
#[unstable(feature = "rustc_contracts", issue = "none")]
1787+
#[allow_internal_unstable(core_intrinsics)]
1788+
#[rustc_builtin_macro]
1789+
pub macro contracts_ensures($item:item) {
1790+
/* compiler built-in */
1791+
}
1792+
1793+
/// Attribute macro applied to a function to give it a precondition.
1794+
///
1795+
/// The attribute carries an argument token-tree which is
1796+
/// eventually parsed as an boolean expression with access to the
1797+
/// function's formal parameters
1798+
#[cfg(not(bootstrap))]
1799+
#[unstable(feature = "rustc_contracts", issue = "none")]
1800+
#[allow_internal_unstable(core_intrinsics)]
1801+
#[rustc_builtin_macro]
1802+
pub macro contracts_requires($item:item) {
1803+
/* compiler built-in */
1804+
}
1805+
17801806
/// Attribute macro applied to a function to register it as a handler for allocation failure.
17811807
///
17821808
/// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html).

0 commit comments

Comments
 (0)