Skip to content

Commit b6facd2

Browse files
author
Verus CI Bot
committed
Merge branch 'pr-2132' into update-test
2 parents b7caec7 + 7b17725 commit b6facd2

4 files changed

Lines changed: 204 additions & 20 deletions

File tree

dependencies/prettyplease/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ mod ty;
369369
use crate::algorithm::Printer;
370370
use verus_syn::Expr;
371371
use verus_syn::File;
372+
use verus_syn::Pat;
373+
use verus_syn::Type;
372374

373375
// Target line width.
374376
const MARGIN: isize = 89;
@@ -390,3 +392,15 @@ pub fn unparse_expr(e: &Expr) -> String {
390392
p.expr(e, crate::fixup::FixupContext::NONE);
391393
p.eof()
392394
}
395+
396+
pub fn unparse_pat(pat: &Pat) -> String {
397+
let mut p = Printer::new();
398+
p.pat(pat);
399+
p.eof()
400+
}
401+
402+
pub fn unparse_ty(ty: &Type) -> String {
403+
let mut p = Printer::new();
404+
p.ty(ty);
405+
p.eof()
406+
}

source/builtin_macros/src/attr_rewrite.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
/// - Refer to `examples/syntax_attr.rs`.
3737
use proc_macro2::TokenStream;
3838
use quote::{ToTokens, quote, quote_spanned};
39+
use syn::parse::Parser;
3940
use syn::visit_mut::VisitMut;
4041
use syn::{Expr, Item, ItemConst, parse2, spanned::Spanned};
4142

@@ -526,14 +527,92 @@ pub(crate) fn rewrite_verus_spec_on_fun_or_loop(
526527
fun.attrs.retain(|attr| !is_hidden_impl_marker(attr));
527528

528529
let mut new_stream = TokenStream::new();
530+
let mut rustdoc_attrs: Vec<syn::Attribute> = vec![];
531+
if crate::rustdoc::env_rustdoc() {
532+
let mut verus_fun: verus_syn::ItemFn = syn_to_verus_syn(fun.clone());
533+
verus_fun.sig.spec = spec_attr.spec.clone();
534+
535+
// Set return variable name
536+
if let Some((verus_syn::Pat::Ident(pat_ident), _)) = &spec_attr.ret_pat {
537+
if let verus_syn::ReturnType::Type(_, _, opt_name, _) =
538+
&mut verus_fun.sig.output
539+
{
540+
*opt_name = Some(Box::new((
541+
verus_syn::token::Paren::default(),
542+
verus_syn::Pat::Ident(pat_ident.clone()),
543+
verus_syn::Token![:](pat_ident.span()),
544+
)));
545+
}
546+
}
547+
548+
crate::rustdoc::process_item_fn(&mut verus_fun);
549+
550+
for attr in &verus_fun.attrs {
551+
if attr.path().is_ident("doc")
552+
&& attr.to_token_stream().to_string().contains("verusdoc_special_attr")
553+
{
554+
if let Ok(doc_attrs) =
555+
syn::Attribute::parse_outer.parse(attr.to_token_stream().into())
556+
{
557+
rustdoc_attrs.extend(doc_attrs);
558+
}
559+
}
560+
}
561+
}
529562

530563
// Create a copy of unverified function.
531564
// To avoid misuse of the unverified function,
532565
// we add `requires false` and thus prevent verified function to use it.
533566
// Allow unverified code to use the function without changing in/output.
534567
if let Some(with) = &spec_attr.spec.with {
535-
let extra_funs = rewrite_unverified_func(&mut fun, with.with.span(), erase);
568+
let mut extra_funs = rewrite_unverified_func(&mut fun, with.with.span(), erase);
569+
570+
if crate::rustdoc::env_rustdoc() {
571+
if let Some(unverified_fun) = extra_funs.last_mut() {
572+
unverified_fun.attrs.extend(rustdoc_attrs.clone());
573+
}
574+
fun.attrs.push(crate::syntax::mk_rust_attr_syn(
575+
with.with.span(),
576+
"doc",
577+
quote! {hidden},
578+
));
579+
}
536580
extra_funs.iter().for_each(|f| f.to_tokens(&mut new_stream));
581+
} else if crate::rustdoc::env_rustdoc() {
582+
fun.attrs.extend(rustdoc_attrs);
583+
}
584+
585+
// Inject doc attribute in rustdoc mode
586+
if crate::rustdoc::env_rustdoc() {
587+
let mut verus_fun: verus_syn::ItemFn = syn_to_verus_syn(fun.clone());
588+
verus_fun.sig.spec = spec_attr.spec.clone();
589+
590+
// Set return variable name
591+
if let Some((verus_syn::Pat::Ident(pat_ident), _)) = &spec_attr.ret_pat {
592+
if let verus_syn::ReturnType::Type(_, _, opt_name, _) =
593+
&mut verus_fun.sig.output
594+
{
595+
*opt_name = Some(Box::new((
596+
verus_syn::token::Paren::default(),
597+
verus_syn::Pat::Ident(pat_ident.clone()),
598+
verus_syn::Token![:](pat_ident.span()),
599+
)));
600+
}
601+
}
602+
603+
crate::rustdoc::process_item_fn(&mut verus_fun);
604+
605+
for attr in &verus_fun.attrs {
606+
if attr.path().is_ident("doc")
607+
&& attr.to_token_stream().to_string().contains("verusdoc_special_attr")
608+
{
609+
if let Ok(doc_attrs) =
610+
syn::Attribute::parse_outer.parse(attr.to_token_stream().into())
611+
{
612+
fun.attrs.extend(doc_attrs);
613+
}
614+
}
615+
}
537616
}
538617

539618
// Update function signature based on verus_spec.
@@ -936,6 +1015,13 @@ fn rewrite_unverified_func(
9361015
Some(syn::token::Semi { spans: [span] }),
9371016
);
9381017
unverified_fun.attrs_mut().push(mk_verus_attr_syn(span, quote! { external_body }));
1018+
if !crate::rustdoc::env_rustdoc() {
1019+
unverified_fun.attrs_mut().push(crate::syntax::mk_rust_attr_syn(
1020+
span,
1021+
"doc",
1022+
quote! {hidden},
1023+
));
1024+
}
9391025
if let Some(block) = unverified_fun.block_mut() {
9401026
// For an unverified function, if it is in keep mode,
9411027
// we erase the function body to avoid using

source/builtin_macros/src/rustdoc.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@
2929
// some data explaining the function mode, param modes, and return mode.
3030

3131
use proc_macro2::Span;
32+
use quote::ToTokens;
3233
use std::iter::FromIterator;
3334
use verus_syn::punctuated::Punctuated;
3435
use verus_syn::spanned::Spanned;
3536
use verus_syn::token;
3637
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,
3940
ReturnType, Signature, TraitItemFn, Type, TypeGroup, TypePath,
4041
};
4142

@@ -103,6 +104,10 @@ fn attr_for_sig(
103104

104105
v.push(encoded_sig_info(sig));
105106

107+
if let Some(with_spec) = &sig.spec.with {
108+
v.push(encoded_str("with", &format_with_spec(with_spec)));
109+
}
110+
106111
match &sig.spec.requires {
107112
Some(es) => {
108113
for expr in es.exprs.exprs.iter() {
@@ -338,6 +343,66 @@ fn encoded_str(kind: &str, data: &str) -> String {
338343
"```rust\n// verusdoc_special_attr ".to_string() + kind + "\n" + data + "\n```"
339344
}
340345

346+
fn format_with_spec(with_spec: &verus_syn::WithSpecOnFn) -> String {
347+
let mut lines: Vec<String> = vec![];
348+
349+
let inputs = format_fn_args(&with_spec.inputs);
350+
for input in inputs {
351+
let input = normalize_ws(input.trim());
352+
lines.push(format!("{input},"));
353+
}
354+
355+
if let Some((_, outputs)) = &with_spec.outputs {
356+
lines.push("->".to_string());
357+
let outputs = format_pat_types(outputs);
358+
for output in outputs {
359+
let output = normalize_ws(output.trim());
360+
lines.push(format!("{output},"));
361+
}
362+
}
363+
364+
lines.join("\n")
365+
}
366+
367+
fn format_pat_types(outputs: &Punctuated<verus_syn::PatType, verus_syn::Token![,]>) -> Vec<String> {
368+
if outputs.is_empty() {
369+
return vec![];
370+
}
371+
372+
outputs.iter().map(format_pat_type).collect()
373+
}
374+
375+
fn format_fn_args(inputs: &Punctuated<FnArg, verus_syn::Token![,]>) -> Vec<String> {
376+
if inputs.is_empty() {
377+
return vec![];
378+
}
379+
inputs.iter().map(format_fn_arg).collect()
380+
}
381+
382+
fn format_fn_arg(arg: &FnArg) -> String {
383+
let tracked = if arg.tracked.is_some() { "tracked " } else { "" };
384+
match &arg.kind {
385+
verus_syn::FnArgKind::Receiver(receiver) => {
386+
let s = normalize_ws(&receiver.to_token_stream().to_string());
387+
format!("{tracked}{s}")
388+
}
389+
verus_syn::FnArgKind::Typed(pt) => {
390+
let s = format_pat_type(pt);
391+
format!("{tracked}{s}")
392+
}
393+
}
394+
}
395+
396+
fn format_pat_type(pt: &verus_syn::PatType) -> String {
397+
let pat = normalize_ws(&verus_prettyplease::unparse_pat(&pt.pat));
398+
let ty = normalize_ws(&verus_prettyplease::unparse_ty(&pt.ty));
399+
format!("{pat}: {ty}")
400+
}
401+
402+
fn normalize_ws(s: &str) -> String {
403+
s.split_whitespace().collect::<Vec<&str>>().join(" ")
404+
}
405+
341406
/// Create an attr that looks like #[doc = "doc_str"]
342407
fn doc_attr_from_string(doc_str: &str, span: Span) -> Attribute {
343408
let path = Path {

source/verusdoc/src/main.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ enum VerusDocAttr {
3333
}
3434

3535
// Types of spec clauses we handle.
36-
static SPEC_NAMES: [&str; 5] = ["requires", "ensures", "returns", "recommends", "body"];
36+
static SPEC_NAMES: [&str; 6] = ["with", "requires", "ensures", "returns", "recommends", "body"];
3737

3838
fn main() {
3939
// Manipulate the auto-generated files in `doc/` to clean them up to make
@@ -256,14 +256,26 @@ fn update_docblock(
256256
}
257257

258258
for spec_name in SPEC_NAMES.iter() {
259-
let code_blocks: Vec<NodeRef> = attrs
259+
let mut code_blocks: Vec<NodeRef> = attrs
260260
.iter()
261261
.filter_map(|a| match a {
262262
VerusDocAttr::Specification(s, nr) if s == spec_name => Some(nr.clone()),
263263
_ => None,
264264
})
265265
.collect();
266266

267+
// De-duplicate identical spec blocks (can happen with #[verus_spec] + rustdoc pass)
268+
let mut seen: Vec<String> = Vec::new();
269+
code_blocks.retain(|nr| {
270+
let text = nr.text_contents();
271+
if seen.iter().any(|t| t == &text) {
272+
false
273+
} else {
274+
seen.push(text);
275+
true
276+
}
277+
});
278+
267279
let is_body = spec_name == &"body";
268280

269281
if code_blocks.len() > 0 && !is_body {
@@ -289,22 +301,29 @@ fn update_docblock(
289301
}
290302

291303
// Add mode info to the signature
292-
293-
for attr in attrs.iter() {
294-
match attr {
295-
VerusDocAttr::ModeInfo(doc_mode_info) => {
296-
update_sig_info(
297-
docblock_elem,
298-
UpdateSigMode::DocSigInfo(doc_mode_info),
299-
opt_trait_info,
300-
);
301-
break;
302-
}
303-
VerusDocAttr::BroadcastGroup => {
304-
update_sig_info(docblock_elem, UpdateSigMode::BroadcastGroup, opt_trait_info);
305-
break;
304+
// If there are multiple ModeInfo attrs (caused by the `#[verus_spec]` macro expansion),
305+
// choose the one with a non-empty `ret_name`.
306+
let mode_infos: Vec<&DocSigInfo> = attrs
307+
.iter()
308+
.filter_map(|a| match a {
309+
VerusDocAttr::ModeInfo(info) => Some(info),
310+
_ => None,
311+
})
312+
.collect();
313+
let info_to_use =
314+
mode_infos.iter().find(|info| !info.ret_name.is_empty()).or_else(|| mode_infos.first());
315+
316+
if let Some(info) = info_to_use {
317+
update_sig_info(docblock_elem, UpdateSigMode::DocSigInfo(info), opt_trait_info);
318+
} else {
319+
for attr in attrs.iter() {
320+
match attr {
321+
VerusDocAttr::BroadcastGroup => {
322+
update_sig_info(docblock_elem, UpdateSigMode::BroadcastGroup, opt_trait_info);
323+
break;
324+
}
325+
_ => {}
306326
}
307-
_ => {}
308327
}
309328
}
310329
}

0 commit comments

Comments
 (0)