1use syn::{
2 Attribute, Expr, Ident, Meta, Pat, PatIdent,
3 parse::{Parse, ParseStream, Result},
4 parse_quote,
5 spanned::Spanned,
6};
7
8use crate::{Capture, PostCondition, PreCondition, Spec, annotate::syntax::CaptureExpr};
9
10pub mod syntax;
11use syntax::{Captures, Keyword};
12
13#[cfg(test)]
14mod tests;
15
16impl Parse for Spec {
17 fn parse(input: ParseStream) -> Result<Self> {
18 let raw_spec = syntax::SpecArgs::parse(input)?;
19
20 let mut prev_keyword: Option<Keyword> = None;
21 let mut requires: Vec<PreCondition> = vec![];
22 let mut maintains: Vec<PreCondition> = vec![];
23 let mut captures: Vec<Capture> = vec![];
24 let mut binds_pattern: Option<Pat> = None;
25 let mut ensures: Vec<PostCondition> = vec![];
26
27 for arg in raw_spec.args {
28 match &arg.keyword {
29 Keyword::Requires => {
30 let cfg_attr = find_cfg_attribute(&arg.attrs)?;
31 let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
32 Some(attr.parse_args()?)
33 } else {
34 None
35 };
36 let expr = arg.value.try_into_expr()?;
37 if let Expr::Array(conditions) = expr {
38 for expr in conditions.elems {
39 requires.push(PreCondition {
40 closure: interpret_expr_as_precondition(expr)?,
41 cfg: cfg.clone(),
42 });
43 }
44 } else {
45 requires.push(PreCondition {
46 closure: interpret_expr_as_precondition(expr)?,
47 cfg,
48 });
49 }
50 }
51 Keyword::Maintains => {
52 let cfg_attr = find_cfg_attribute(&arg.attrs)?;
53 let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
54 Some(attr.parse_args()?)
55 } else {
56 None
57 };
58 let expr = arg.value.try_into_expr()?;
59 if let Expr::Array(conditions) = expr {
60 for expr in conditions.elems {
61 maintains.push(PreCondition {
62 closure: interpret_expr_as_precondition(expr)?,
63 cfg: cfg.clone(),
64 });
65 }
66 } else {
67 maintains.push(PreCondition {
68 closure: interpret_expr_as_precondition(expr)?,
69 cfg,
70 });
71 }
72 }
73 Keyword::Captures => {
74 let cfg_attr = find_cfg_attribute(&arg.attrs)?;
75 if cfg_attr.is_some() {
76 return Err(syn::Error::new(
77 cfg_attr.span(),
78 "`cfg` attribute is not supported on `captures`",
79 ));
80 }
81 if !captures.is_empty() {
82 return Err(syn::Error::new(
83 arg.keyword_span,
84 "at most one `captures` parameter is allowed; to capture multiple values, use a list: `captures: [expr1, expr2, ...]`",
85 ));
86 }
87 let capture_list = arg.value.try_into_captures()?;
88 match capture_list {
89 Captures::One(capture_expr) => {
90 captures
91 .push(interpret_capture_expr_as_capture(*capture_expr.clone())?);
92 }
93 Captures::Many { elems, .. } => {
94 for capture_expr in elems {
95 captures.push(interpret_capture_expr_as_capture(capture_expr)?);
96 }
97 }
98 }
99 }
100 Keyword::Binds => {
101 let cfg_attr = find_cfg_attribute(&arg.attrs)?;
102 if cfg_attr.is_some() {
103 return Err(syn::Error::new(
104 cfg_attr.span(),
105 "`cfg` attribute is not supported on `binds`",
106 ));
107 }
108 if binds_pattern.is_some() {
109 return Err(syn::Error::new(
110 arg.keyword_span,
111 "multiple `binds` parameters are not allowed",
112 ));
113 }
114 let pattern = arg.value.try_into_pat()?;
115 binds_pattern = Some(pattern);
116 }
117 Keyword::Ensures => {
118 let cfg_attr = find_cfg_attribute(&arg.attrs)?;
119 let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
120 Some(attr.parse_args()?)
121 } else {
122 None
123 };
124 let expr = arg.value.try_into_expr()?;
125 let default_pattern = binds_pattern.clone().unwrap_or(parse_quote! { output });
126 if let Expr::Array(conditions) = expr {
127 for expr in conditions.elems {
128 ensures.push(PostCondition {
129 closure: interpret_expr_as_postcondition(
130 expr,
131 default_pattern.clone(),
132 )?,
133 cfg: cfg.clone(),
134 });
135 }
136 } else {
137 ensures.push(PostCondition {
138 closure: interpret_expr_as_postcondition(expr, default_pattern)?,
139 cfg,
140 });
141 }
142 }
143 Keyword::Unknown(ident) => {
144 return Err(syn::Error::new(
145 arg.keyword_span,
146 format!("unknown spec keyword `{ident}`"),
147 ));
148 }
149 }
150
151 if let Some(prev_keyword) = prev_keyword
152 && arg.keyword < prev_keyword
153 {
154 return Err(syn::Error::new(
155 arg.keyword_span,
156 "parameters are out of order: their order must be `requires`, `maintains`, `captures`, `binds`, `ensures`",
157 ));
158 }
159 prev_keyword = Some(arg.keyword);
160 }
161
162 Ok(Spec {
163 requires,
164 maintains,
165 captures,
166 ensures,
167 span: input.span(),
168 })
169 }
170}
171
172fn interpret_capture_expr_as_capture(capture_expr: CaptureExpr) -> Result<Capture> {
174 let span = capture_expr.span();
175 let CaptureExpr { expr, as_, pat } = capture_expr;
176
177 match (expr, as_, pat) {
178 (Some(expr), Some(_), Some(pat)) => Ok(Capture { expr, pat }),
180
181 (Some(ref expr @ Expr::Path(ref path)), None, None)
183 if path.path.segments.len() == 1
184 && path.path.leading_colon.is_none()
185 && path.attrs.is_empty()
186 && path.qself.is_none() =>
187 {
188 let ident = &path.path.segments[0].ident;
190 let ident_alias = Ident::new(&format!("old_{}", ident), ident.span());
191 let pat = Pat::Ident(PatIdent {
192 ident: ident_alias,
193 attrs: vec![],
194 mutability: None,
195 by_ref: None,
196 subpat: None,
197 });
198 Ok(Capture {
199 expr: expr.clone(),
200 pat,
201 })
202 }
203
204 (Some(_), Some(_), None) => {
206 Err(syn::Error::new_spanned(as_, "expected pattern after `as`"))
207 }
208
209 (Some(expr), None, None) => Err(syn::Error::new_spanned(
211 expr,
212 "complex expression must be bound/descructured: <expression> `as` <pattern>",
213 )),
214
215 (Some(_), None, Some(pat)) => Err(syn::Error::new_spanned(pat, "expected `as` <pattern>")),
217
218 (None, _, _) => Err(syn::Error::new(
220 span,
221 "expected capture: <expression> `as` <pattern>",
222 )),
223 }
224}
225
226fn interpret_expr_as_precondition(expr: Expr) -> Result<syn::ExprClosure> {
229 match expr {
230 Expr::Closure(closure) => {
232 if closure.inputs.is_empty() {
233 Ok(closure)
234 } else {
235 Err(syn::Error::new_spanned(
236 closure.or1_token,
237 format!(
238 "precondition closure must have no arguments, found {}",
239 closure.inputs.len()
240 ),
241 ))
242 }
243 }
244 expr => Ok(syn::ExprClosure {
246 attrs: vec![],
247 lifetimes: None,
248 constness: None,
249 movability: None,
250 asyncness: None,
251 capture: None,
252 or1_token: Default::default(),
253 inputs: syn::punctuated::Punctuated::new(),
254 or2_token: Default::default(),
255 output: syn::ReturnType::Default,
256 body: Box::new(expr),
257 }),
258 }
259}
260
261fn interpret_expr_as_postcondition(expr: Expr, default_binding: Pat) -> Result<syn::ExprClosure> {
265 match expr {
266 Expr::Closure(closure) => {
268 if closure.inputs.len() == 1 {
269 Ok(closure)
270 } else {
271 Err(syn::Error::new_spanned(
272 closure.or1_token,
273 format!(
274 "postcondition closure must have exactly one argument, found {}",
275 closure.inputs.len()
276 ),
277 ))
278 }
279 }
280 expr => Ok(syn::ExprClosure {
282 attrs: vec![],
283 lifetimes: None,
284 constness: None,
285 movability: None,
286 asyncness: None,
287 capture: None,
288 or1_token: Default::default(),
289 inputs: syn::punctuated::Punctuated::from_iter([default_binding]),
290 or2_token: Default::default(),
291 output: syn::ReturnType::Default,
292 body: Box::new(expr),
293 }),
294 }
295}
296
297fn find_cfg_attribute(attrs: &[Attribute]) -> Result<Option<&Attribute>> {
298 let mut cfg_attr: Option<&Attribute> = None;
299
300 for attr in attrs {
301 if attr.path().is_ident("cfg") {
302 if cfg_attr.is_some() {
303 return Err(syn::Error::new(
304 attr.span(),
305 "multiple `cfg` attributes are not supported",
306 ));
307 }
308 cfg_attr = Some(attr);
309 } else {
310 return Err(syn::Error::new(
311 attr.span(),
312 "unsupported attribute; only `cfg` is allowed",
313 ));
314 }
315 }
316
317 Ok(cfg_attr)
318}