1use rustc_ast::token;
2use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
3use rustc_errors::ErrorGuaranteed;
4use rustc_expand::base::{AttrProcMacro, ExtCtxt};
5use rustc_span::Span;
6use rustc_span::symbol::{Ident, Symbol, kw};
7
8pub(crate) struct ExpandRequires;
9
10pub(crate) struct ExpandEnsures;
11
12impl AttrProcMacro for ExpandRequires {
13 fn expand<'cx>(
14 &self,
15 ecx: &'cx mut ExtCtxt<'_>,
16 span: Span,
17 annotation: TokenStream,
18 annotated: TokenStream,
19 ) -> Result<TokenStream, ErrorGuaranteed> {
20 expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractRequires)
21 }
22}
23
24impl AttrProcMacro for ExpandEnsures {
25 fn expand<'cx>(
26 &self,
27 ecx: &'cx mut ExtCtxt<'_>,
28 span: Span,
29 annotation: TokenStream,
30 annotated: TokenStream,
31 ) -> Result<TokenStream, ErrorGuaranteed> {
32 expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractEnsures)
33 }
34}
35
36fn expand_contract_clause(
48 ecx: &mut ExtCtxt<'_>,
49 attr_span: Span,
50 annotated: TokenStream,
51 inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>,
52) -> Result<TokenStream, ErrorGuaranteed> {
53 let mut new_tts = ::alloc::vec::Vec::new()vec![];
54 let mut cursor = annotated.iter();
55
56 let is_kw = |tt: &TokenTree, sym: Symbol| {
57 if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false }
58 };
59
60 if cursor
62 .find(|tt| {
63 new_tts.push((*tt).clone());
64 is_kw(tt, kw::Fn)
65 })
66 .is_none()
67 {
68 return Err(ecx
69 .sess
70 .dcx()
71 .span_err(attr_span, "contract annotations can only be used on functions"));
72 }
73
74 if new_tts.iter().any(|tt| is_kw(tt, kw::Async) || is_kw(tt, kw::Gen)) {
76 return Err(ecx.sess.dcx().span_err(
77 attr_span,
78 "contract annotations are not yet supported on async or gen functions",
79 ));
80 }
81
82 let next_tt = loop {
84 let Some(tt) = cursor.next() else {
85 return Err(ecx.sess.dcx().span_err(
86 attr_span,
87 "contract annotations is only supported in functions with bodies",
88 ));
89 };
90 if cursor.peek().is_none() {
92 if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt {
93 break tt;
94 } else {
95 return Err(ecx.sess.dcx().span_err(
96 attr_span,
97 "contract annotations is only supported in functions with bodies",
98 ));
99 }
100 }
101
102 if is_kw(tt, kw::Where) {
103 break tt;
104 }
105 new_tts.push(tt.clone());
106 };
107
108 inject(&mut new_tts)?;
114
115 new_tts.push(next_tt.clone());
118 while let Some(tt) = cursor.next() {
119 new_tts.push(tt.clone());
120 if cursor.peek().is_none()
121 && !#[allow(non_exhaustive_omitted_patterns)] match tt {
TokenTree::Delimited(_, _, token::Delimiter::Brace, _) => true,
_ => false,
}matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _))
122 {
123 return Err(ecx.sess.dcx().span_err(
124 attr_span,
125 "contract annotations is only supported in functions with bodies",
126 ));
127 }
128 }
129
130 Ok(TokenStream::new(new_tts))
131}
132
133fn expand_contract_clause_tts(
134 ecx: &mut ExtCtxt<'_>,
135 attr_span: Span,
136 annotation: TokenStream,
137 annotated: TokenStream,
138 clause_keyword: rustc_span::Symbol,
139) -> Result<TokenStream, ErrorGuaranteed> {
140 if annotation.is_empty() {
141 let (name, example) = if clause_keyword == kw::ContractRequires {
142 ("requires", "condition")
143 } else {
144 ("ensures", "|result: &T| condition")
145 };
146 ecx.sess.dcx().span_err(
147 attr_span,
148 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` attribute requires an argument, e.g., `#[{0}({1})]`",
name, example))
})format!("`{name}` attribute requires an argument, e.g., `#[{name}({example})]`"),
149 );
150 return Ok(annotated);
153 }
154
155 let feature_span = ecx.with_def_site_ctxt(attr_span);
156 expand_contract_clause(ecx, attr_span, annotated, |new_tts| {
157 new_tts.push(TokenTree::Token(
158 token::Token::from_ast_ident(Ident::new(clause_keyword, feature_span)),
159 Spacing::Joint,
160 ));
161 new_tts.push(TokenTree::Delimited(
162 DelimSpan::from_single(attr_span),
163 DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
164 token::Delimiter::Brace,
165 annotation,
166 ));
167 Ok(())
168 })
169}