Skip to content

Commit d11e749

Browse files
authored
RUST-2103 Preliminary improvement for action rustdocs: find (#1248)
1 parent b821bb8 commit d11e749

File tree

6 files changed

+177
-53
lines changed

6 files changed

+177
-53
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ tracing = { version = "0.1.36", optional = true }
114114
typed-builder = "0.10.0"
115115
webpki-roots = "0.25.2"
116116
zstd = { version = "0.11.2", optional = true }
117+
macro_magic = "0.5.1"
117118

118119
[dependencies.pbkdf2]
119120
version = "0.11.0"

deny.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ allow = [
3434
"MIT",
3535
"Apache-2.0",
3636
"Apache-2.0 WITH LLVM-exception",
37+
"CC0-1.0",
3738
"ISC",
3839
"OpenSSL",
3940
"BSD-2-Clause",

macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ license = "Apache-2.0"
88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
99

1010
[dependencies]
11+
macro_magic = { version = "0.5.1", features = ["proc_support"] }
1112
proc-macro2 = "1.0.78"
1213
quote = "1.0.35"
1314
syn = { version = "2.0.52", features = ["full", "parsing", "proc-macro", "extra-traits"] }

macros/src/lib.rs

Lines changed: 167 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extern crate proc_macro;
22

3+
use macro_magic::import_tokens_attr;
34
use quote::{quote, ToTokens};
45
use syn::{
56
braced,
@@ -13,10 +14,14 @@ use syn::{
1314
Block,
1415
Error,
1516
Expr,
17+
Fields,
1618
GenericArgument,
1719
Generics,
1820
Ident,
21+
ImplItem,
1922
ImplItemFn,
23+
ItemImpl,
24+
ItemStruct,
2025
Lifetime,
2126
Lit,
2227
Meta,
@@ -25,6 +30,7 @@ use syn::{
2530
PathSegment,
2631
Token,
2732
Type,
33+
Visibility,
2834
};
2935

3036
/// Generates:
@@ -211,6 +217,12 @@ fn parse_name(input: ParseStream, name: &str) -> syn::Result<()> {
211217
Ok(())
212218
}
213219

220+
macro_rules! compile_error {
221+
($span:expr, $($message:tt)+) => {{
222+
return Error::new($span, format!($($message)+)).into_compile_error().into();
223+
}};
224+
}
225+
214226
/// Enables rustdoc links to types that link individually to each type
215227
/// component.
216228
#[proc_macro_attribute]
@@ -242,11 +254,7 @@ pub fn deeplink(
242254
let rest = &text[ix + 2..];
243255
let end = match rest.find(']') {
244256
Some(v) => v,
245-
None => {
246-
return Error::new(attr.span(), "unterminated d[")
247-
.into_compile_error()
248-
.into()
249-
}
257+
None => compile_error!(attr.span(), "unterminated d["),
250258
};
251259
let body = &rest[..end];
252260
let post = &rest[end + 1..];
@@ -322,20 +330,18 @@ pub fn option_setters(input: proc_macro::TokenStream) -> proc_macro::TokenStream
322330
setters,
323331
} = parse_macro_input!(input as OptionSettersList);
324332

325-
let extras = opt_field_name.map(|name| {
326-
quote! {
327-
#[allow(unused)]
328-
fn options(&mut self) -> &mut #opt_field_type {
329-
self.#name.get_or_insert_with(<#opt_field_type>::default)
330-
}
333+
let extras = quote! {
334+
#[allow(unused)]
335+
fn options(&mut self) -> &mut #opt_field_type {
336+
self.#opt_field_name.get_or_insert_with(<#opt_field_type>::default)
337+
}
331338

332-
/// Set all options. Note that this will replace all previous values set.
333-
pub fn with_options(mut self, value: impl Into<Option<#opt_field_type>>) -> Self {
334-
self.#name = value.into();
335-
self
336-
}
339+
/// Set all options. Note that this will replace all previous values set.
340+
pub fn with_options(mut self, value: impl Into<Option<#opt_field_type>>) -> Self {
341+
self.#opt_field_name = value.into();
342+
self
337343
}
338-
});
344+
};
339345

340346
let setters: Vec<_> = setters
341347
.into_iter()
@@ -350,7 +356,7 @@ pub fn option_setters(input: proc_macro::TokenStream) -> proc_macro::TokenStream
350356
|| path_eq(&type_, &["bson", "Bson"])
351357
{
352358
(quote! { impl Into<#type_> }, quote! { value.into() })
353-
} else if let Some(t) = vec_arg(&type_) {
359+
} else if let Some(t) = inner_type(&type_, "Vec") {
354360
(
355361
quote! { impl IntoIterator<Item = #t> },
356362
quote! { value.into_iter().collect() },
@@ -376,12 +382,12 @@ pub fn option_setters(input: proc_macro::TokenStream) -> proc_macro::TokenStream
376382
.into()
377383
}
378384

379-
fn vec_arg(path: &Path) -> Option<&Type> {
385+
fn inner_type<'a>(path: &'a Path, outer: &str) -> Option<&'a Type> {
380386
if path.segments.len() != 1 {
381387
return None;
382388
}
383389
let PathSegment { ident, arguments } = path.segments.first()?;
384-
if ident != "Vec" {
390+
if ident != outer {
385391
return None;
386392
}
387393
let args = if let PathArguments::AngleBracketed(angle) = arguments {
@@ -415,20 +421,15 @@ fn path_eq(path: &Path, segments: &[&str]) -> bool {
415421
}
416422

417423
struct OptionSettersList {
418-
opt_field_name: Option<Ident>,
424+
opt_field_name: Ident,
419425
opt_field_type: Type,
420426
setters: Vec<OptionSetter>,
421427
}
422428

423429
impl Parse for OptionSettersList {
424430
fn parse(input: ParseStream) -> syn::Result<Self> {
425-
let opt_field_name = if input.peek2(Token![:]) {
426-
let val = input.parse()?;
427-
input.parse::<Token![:]>()?;
428-
Some(val)
429-
} else {
430-
None
431-
};
431+
let opt_field_name = input.parse()?;
432+
input.parse::<Token![:]>()?;
432433
let opt_field_type = input.parse()?;
433434
input.parse::<Token![;]>()?;
434435
let setters = input
@@ -458,3 +459,141 @@ impl Parse for OptionSetter {
458459
Ok(Self { attrs, name, type_ })
459460
}
460461
}
462+
463+
#[import_tokens_attr]
464+
#[proc_macro_attribute]
465+
pub fn option_setters_2(
466+
attr: proc_macro::TokenStream,
467+
item: proc_macro::TokenStream,
468+
) -> proc_macro::TokenStream {
469+
let opt_struct = parse_macro_input!(attr as ItemStruct);
470+
let mut impl_in = parse_macro_input!(item as ItemImpl);
471+
472+
// Gather information about each option struct field
473+
struct OptInfo {
474+
name: Ident,
475+
attrs: Vec<Attribute>,
476+
type_: Path,
477+
}
478+
let mut opt_info = vec![];
479+
let fields = match &opt_struct.fields {
480+
Fields::Named(f) => &f.named,
481+
_ => compile_error!(opt_struct.span(), "options struct must have named fields"),
482+
};
483+
for field in fields {
484+
if !matches!(field.vis, Visibility::Public(..)) {
485+
continue;
486+
}
487+
// name
488+
let name = match &field.ident {
489+
Some(f) => f.clone(),
490+
None => continue,
491+
};
492+
// doc and cfg attrs
493+
let mut attrs = vec![];
494+
for attr in &field.attrs {
495+
if attr.path().is_ident("doc") || attr.path().is_ident("cfg") {
496+
attrs.push(attr.clone());
497+
}
498+
}
499+
// type, unwrapped from `Option`
500+
let outer = match &field.ty {
501+
Type::Path(ty) => &ty.path,
502+
_ => compile_error!(field.span(), "invalid type"),
503+
};
504+
let type_ = match inner_type(outer, "Option") {
505+
Some(Type::Path(ty)) => ty.path.clone(),
506+
_ => compile_error!(field.span(), "invalid type"),
507+
};
508+
509+
opt_info.push(OptInfo { name, attrs, type_ });
510+
}
511+
512+
// Append utility fns to `impl` block item list
513+
let opt_field_type = &opt_struct.ident;
514+
impl_in.items.push(parse_quote! {
515+
#[allow(unused)]
516+
fn options(&mut self) -> &mut #opt_field_type {
517+
self.options.get_or_insert_with(<#opt_field_type>::default)
518+
}
519+
});
520+
impl_in.items.push(parse_quote! {
521+
/// Set all options. Note that this will replace all previous values set.
522+
pub fn with_options(mut self, value: impl Into<Option<#opt_field_type>>) -> Self {
523+
self.options = value.into();
524+
self
525+
}
526+
});
527+
// Append setter fns to `impl` block item list
528+
for OptInfo { name, attrs, type_ } in opt_info {
529+
let (accept, value) = if type_.is_ident("String")
530+
|| type_.is_ident("Bson")
531+
|| path_eq(&type_, &["bson", "Bson"])
532+
{
533+
(quote! { impl Into<#type_> }, quote! { value.into() })
534+
} else if let Some(t) = inner_type(&type_, "Vec") {
535+
(
536+
quote! { impl IntoIterator<Item = #t> },
537+
quote! { value.into_iter().collect() },
538+
)
539+
} else {
540+
(quote! { #type_ }, quote! { value })
541+
};
542+
impl_in.items.push(parse_quote! {
543+
#(#attrs)*
544+
pub fn #name(mut self, value: #accept) -> Self {
545+
self.options().#name = Some(#value);
546+
self
547+
}
548+
})
549+
}
550+
551+
// All done.
552+
impl_in.to_token_stream().into()
553+
}
554+
555+
#[import_tokens_attr]
556+
#[proc_macro_attribute]
557+
pub fn options_doc(
558+
attr: proc_macro::TokenStream,
559+
item: proc_macro::TokenStream,
560+
) -> proc_macro::TokenStream {
561+
let setters = parse_macro_input!(attr as ItemImpl);
562+
let mut impl_fn = parse_macro_input!(item as ImplItemFn);
563+
564+
// Collect a list of names from the setters impl
565+
let mut setter_names = vec![];
566+
for item in &setters.items {
567+
match item {
568+
ImplItem::Fn(item) if matches!(item.vis, Visibility::Public(..)) => {
569+
setter_names.push(item.sig.ident.to_token_stream().to_string());
570+
}
571+
_ => continue,
572+
}
573+
}
574+
575+
// Get the rustdoc path to the action type, i.e. the type with generic arguments stripped
576+
let mut doc_path = match &*setters.self_ty {
577+
Type::Path(p) => p.path.clone(),
578+
t => compile_error!(t.span(), "invalid options doc argument"),
579+
};
580+
for seg in &mut doc_path.segments {
581+
seg.arguments = PathArguments::None;
582+
}
583+
let doc_path = doc_path.to_token_stream().to_string();
584+
585+
// Add the list of setters to the rustdoc for the fn
586+
impl_fn.attrs.push(parse_quote! {
587+
#[doc = ""]
588+
});
589+
impl_fn.attrs.push(parse_quote! {
590+
#[doc = "These methods can be chained before calling `.await` to set options:"]
591+
});
592+
for name in setter_names {
593+
let docstr = format!(" * [`{0}`]({1}::{0})", name, doc_path);
594+
impl_fn.attrs.push(parse_quote! {
595+
#[doc = #docstr]
596+
});
597+
}
598+
impl_fn.into_token_stream().into()
599+
}

src/action/find.rs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::time::Duration;
22

33
use bson::{Bson, Document};
4+
use macro_magic::export_tokens;
5+
use mongodb_internal_macros::{option_setters_2, options_doc};
46
use serde::de::DeserializeOwned;
57

68
use crate::{
@@ -24,6 +26,7 @@ impl<T: Send + Sync> Collection<T> {
2426
/// `await` will return d[`Result<Cursor<T>>`] (or d[`Result<SessionCursor<T>>`] if a session is
2527
/// provided).
2628
#[deeplink]
29+
#[options_doc(find_setters)]
2730
pub fn find(&self, filter: Document) -> Find<'_, T> {
2831
Find {
2932
coll: self,
@@ -81,32 +84,9 @@ pub struct Find<'a, T: Send + Sync, Session = ImplicitSession> {
8184
session: Session,
8285
}
8386

87+
#[option_setters_2(crate::coll::options::FindOptions)]
88+
#[export_tokens(find_setters)]
8489
impl<'a, T: Send + Sync, Session> Find<'a, T, Session> {
85-
option_setters!(options: FindOptions;
86-
allow_disk_use: bool,
87-
allow_partial_results: bool,
88-
batch_size: u32,
89-
comment: Bson,
90-
cursor_type: CursorType,
91-
hint: Hint,
92-
limit: i64,
93-
max: Document,
94-
max_await_time: Duration,
95-
max_scan: u64,
96-
max_time: Duration,
97-
min: Document,
98-
no_cursor_timeout: bool,
99-
projection: Document,
100-
read_concern: ReadConcern,
101-
return_key: bool,
102-
selection_criteria: SelectionCriteria,
103-
show_record_id: bool,
104-
skip: u64,
105-
sort: Document,
106-
collation: Collation,
107-
let_vars: Document,
108-
);
109-
11090
/// Use the provided session when running the operation.
11191
pub fn session<'s>(
11292
self,

src/coll/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::time::Duration;
22

3+
use macro_magic::export_tokens;
34
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
45
use serde_with::skip_serializing_none;
56
use typed_builder::TypedBuilder;
@@ -760,6 +761,7 @@ pub struct DistinctOptions {
760761
#[builder(field_defaults(default, setter(into)))]
761762
#[serde(rename_all = "camelCase")]
762763
#[non_exhaustive]
764+
#[export_tokens]
763765
pub struct FindOptions {
764766
/// Enables writing to temporary files by the server. When set to true, the find operation can
765767
/// write data to the _tmp subdirectory in the dbPath directory. Only supported in server

0 commit comments

Comments
 (0)