|
29 | 29 | // some data explaining the function mode, param modes, and return mode. |
30 | 30 |
|
31 | 31 | use proc_macro2::Span; |
| 32 | +use quote::ToTokens; |
32 | 33 | use std::iter::FromIterator; |
33 | 34 | use verus_syn::punctuated::Punctuated; |
34 | 35 | use verus_syn::spanned::Spanned; |
35 | 36 | use verus_syn::token; |
36 | 37 | use verus_syn::{ |
37 | | - AssumeSpecification, AttrStyle, Attribute, Block, Expr, ExprBlock, ExprPath, FnMode, Ident, |
38 | | - ImplItemFn, ItemFn, Pat, PatIdent, Path, PathArguments, PathSegment, Publish, QSelf, |
| 38 | + AssumeSpecification, AttrStyle, Attribute, Block, Expr, ExprBlock, ExprPath, FnArg, FnMode, |
| 39 | + Ident, ImplItemFn, ItemFn, Pat, PatIdent, Path, PathArguments, PathSegment, Publish, QSelf, |
39 | 40 | ReturnType, Signature, TraitItemFn, Type, TypeGroup, TypePath, |
40 | 41 | }; |
41 | 42 |
|
@@ -103,6 +104,10 @@ fn attr_for_sig( |
103 | 104 |
|
104 | 105 | v.push(encoded_sig_info(sig)); |
105 | 106 |
|
| 107 | + if let Some(with_spec) = &sig.spec.with { |
| 108 | + v.push(encoded_str("with", &format_with_spec(with_spec))); |
| 109 | + } |
| 110 | + |
106 | 111 | match &sig.spec.requires { |
107 | 112 | Some(es) => { |
108 | 113 | for expr in es.exprs.exprs.iter() { |
@@ -330,6 +335,89 @@ fn encoded_str(kind: &str, data: &str) -> String { |
330 | 335 | "```rust\n// verusdoc_special_attr ".to_string() + kind + "\n" + data + "\n```" |
331 | 336 | } |
332 | 337 |
|
| 338 | +fn format_with_spec(with_spec: &verus_syn::WithSpecOnFn) -> String { |
| 339 | + let mut lines: Vec<String> = vec![]; |
| 340 | + |
| 341 | + let inputs = format_fn_args(&with_spec.inputs); |
| 342 | + for input in inputs { |
| 343 | + let input = normalize_ws(input.trim()); |
| 344 | + lines.push(format!("{input},")); |
| 345 | + } |
| 346 | + |
| 347 | + if let Some((_, outputs)) = &with_spec.outputs { |
| 348 | + lines.push("->".to_string()); |
| 349 | + let outputs = format_pat_types(outputs); |
| 350 | + for output in outputs { |
| 351 | + let output = normalize_ws(output.trim()); |
| 352 | + lines.push(format!("{output},")); |
| 353 | + } |
| 354 | + } |
| 355 | + |
| 356 | + lines.join("\n") |
| 357 | +} |
| 358 | + |
| 359 | +fn format_pat_types(outputs: &Punctuated<verus_syn::PatType, verus_syn::Token![,]>) -> Vec<String> { |
| 360 | + if outputs.is_empty() { |
| 361 | + return vec![]; |
| 362 | + } |
| 363 | + |
| 364 | + outputs.iter().map(format_pat_type).collect() |
| 365 | +} |
| 366 | + |
| 367 | +fn format_fn_args(inputs: &Punctuated<FnArg, verus_syn::Token![,]>) -> Vec<String> { |
| 368 | + if inputs.is_empty() { |
| 369 | + return vec![]; |
| 370 | + } |
| 371 | + inputs.iter().map(format_fn_arg).collect() |
| 372 | +} |
| 373 | + |
| 374 | +fn format_fn_arg(arg: &FnArg) -> String { |
| 375 | + let tracked = if arg.tracked.is_some() { "tracked " } else { "" }; |
| 376 | + match &arg.kind { |
| 377 | + verus_syn::FnArgKind::Receiver(receiver) => { |
| 378 | + let s = normalize_ws(&receiver.to_token_stream().to_string()); |
| 379 | + format!("{tracked}{s}") |
| 380 | + } |
| 381 | + verus_syn::FnArgKind::Typed(pt) => { |
| 382 | + let s = format_pat_type(pt); |
| 383 | + format!("{tracked}{s}") |
| 384 | + } |
| 385 | + } |
| 386 | +} |
| 387 | + |
| 388 | +fn format_pat_type(pt: &verus_syn::PatType) -> String { |
| 389 | + let pat = normalize_ws(&pt.pat.to_token_stream().to_string()); |
| 390 | + let ty = tighten_type_spacing(&normalize_ws(&pt.ty.to_token_stream().to_string())); |
| 391 | + format!("{pat}: {ty}") |
| 392 | +} |
| 393 | + |
| 394 | +fn normalize_ws(s: &str) -> String { |
| 395 | + s.split_whitespace().collect::<Vec<&str>>().join(" ") |
| 396 | +} |
| 397 | + |
| 398 | +fn tighten_type_spacing(s: &str) -> String { |
| 399 | + let bytes = s.as_bytes(); |
| 400 | + let mut out = String::with_capacity(bytes.len()); |
| 401 | + let mut i = 0usize; |
| 402 | + while i < bytes.len() { |
| 403 | + let b = bytes[i]; |
| 404 | + if b == b' ' { |
| 405 | + let prev = if i > 0 { bytes[i - 1] } else { 0 }; |
| 406 | + let next = if i + 1 < bytes.len() { bytes[i + 1] } else { 0 }; |
| 407 | + let remove = |
| 408 | + matches!(prev, b'<' | b'(' | b',' | b':') || matches!(next, b'>' | b')' | b','); |
| 409 | + if !remove { |
| 410 | + out.push(' '); |
| 411 | + } |
| 412 | + i += 1; |
| 413 | + continue; |
| 414 | + } |
| 415 | + out.push(b as char); |
| 416 | + i += 1; |
| 417 | + } |
| 418 | + out |
| 419 | +} |
| 420 | + |
333 | 421 | /// Create an attr that looks like #[doc = "doc_str"] |
334 | 422 | fn doc_attr_from_string(doc_str: &str, span: Span) -> Attribute { |
335 | 423 | let path = Path { |
|
0 commit comments