Skip to content

Commit a05f150

Browse files
authored
RUST-1860 Accept ergonomic conversions for specific option types (#1067)
1 parent ec3a644 commit a05f150

File tree

3 files changed

+154
-74
lines changed

3 files changed

+154
-74
lines changed

action_macro/src/lib.rs

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

3-
use proc_macro2::Span;
43
use quote::{quote, ToTokens};
54
use syn::{
65
braced,
@@ -10,17 +9,20 @@ use syn::{
109
parse_quote,
1110
parse_quote_spanned,
1211
spanned::Spanned,
13-
visit_mut::VisitMut,
12+
Attribute,
1413
Block,
1514
Error,
1615
Expr,
16+
GenericArgument,
1717
Generics,
1818
Ident,
1919
ImplItemFn,
2020
Lifetime,
2121
Lit,
2222
Meta,
23+
Path,
2324
PathArguments,
25+
PathSegment,
2426
Token,
2527
Type,
2628
};
@@ -55,23 +57,10 @@ pub fn action_impl(
5557
}
5658

5759
let sync_run = if let Some(sync_type) = sync_type {
58-
// In expression position, the type needs to be of the form Foo::<Bar>, not Foo<Bar>
59-
let mut formal = sync_type.clone();
60-
struct Visitor;
61-
impl VisitMut for Visitor {
62-
fn visit_path_segment_mut(&mut self, segment: &mut syn::PathSegment) {
63-
if let PathArguments::AngleBracketed(args) = &mut segment.arguments {
64-
if args.colon2_token.is_none() {
65-
args.colon2_token = Some(Token![::](Span::call_site()));
66-
}
67-
}
68-
}
69-
}
70-
syn::visit_mut::visit_type_mut(&mut Visitor, &mut formal);
7160
quote! {
7261
/// Synchronously execute this action.
7362
pub fn run(self) -> Result<#sync_type> {
74-
crate::sync::TOKIO_RUNTIME.block_on(std::future::IntoFuture::into_future(self)).map(#formal::new)
63+
crate::sync::TOKIO_RUNTIME.block_on(std::future::IntoFuture::into_future(self)).map(<#sync_type>::new)
7564
}
7665
}
7766
} else {
@@ -324,3 +313,148 @@ fn text_link(text: &str) -> String {
324313
}
325314
out.concat()
326315
}
316+
317+
#[proc_macro]
318+
pub fn option_setters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
319+
let OptionSettersList {
320+
opt_field_name,
321+
opt_field_type,
322+
setters,
323+
} = parse_macro_input!(input as OptionSettersList);
324+
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+
}
331+
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+
}
337+
}
338+
});
339+
340+
let setters: Vec<_> = setters
341+
.into_iter()
342+
.map(|OptionSetter { attrs, name, type_ }| {
343+
let docstr = format!(
344+
"Set the [`{}::{}`] option.",
345+
opt_field_type.to_token_stream(),
346+
name
347+
);
348+
let (accept, value) = if type_.is_ident("String")
349+
|| type_.is_ident("Bson")
350+
|| path_eq(&type_, &["bson", "Bson"])
351+
{
352+
(quote! { impl Into<#type_> }, quote! { value.into() })
353+
} else if let Some(t) = vec_arg(&type_) {
354+
(
355+
quote! { impl IntoIterator<Item = #t> },
356+
quote! { value.into_iter().collect() },
357+
)
358+
} else {
359+
(quote! { #type_ }, quote! { value })
360+
};
361+
quote! {
362+
#[doc = #docstr]
363+
#(#attrs)*
364+
pub fn #name(mut self, value: #accept) -> Self {
365+
self.options().#name = Some(#value);
366+
self
367+
}
368+
}
369+
})
370+
.collect();
371+
372+
quote! {
373+
#extras
374+
#(#setters)*
375+
}
376+
.into()
377+
}
378+
379+
fn vec_arg(path: &Path) -> Option<&Type> {
380+
if path.segments.len() != 1 {
381+
return None;
382+
}
383+
let PathSegment { ident, arguments } = path.segments.first()?;
384+
if ident != "Vec" {
385+
return None;
386+
}
387+
let args = if let PathArguments::AngleBracketed(angle) = arguments {
388+
&angle.args
389+
} else {
390+
return None;
391+
};
392+
if args.len() != 1 {
393+
return None;
394+
}
395+
if let GenericArgument::Type(t) = args.first()? {
396+
return Some(t);
397+
}
398+
399+
None
400+
}
401+
402+
fn path_eq(path: &Path, segments: &[&str]) -> bool {
403+
if path.segments.len() != segments.len() {
404+
return false;
405+
}
406+
for (actual, expected) in path.segments.iter().zip(segments.into_iter()) {
407+
if actual.ident != expected {
408+
return false;
409+
}
410+
if !actual.arguments.is_empty() {
411+
return false;
412+
}
413+
}
414+
true
415+
}
416+
417+
struct OptionSettersList {
418+
opt_field_name: Option<Ident>,
419+
opt_field_type: Type,
420+
setters: Vec<OptionSetter>,
421+
}
422+
423+
impl Parse for OptionSettersList {
424+
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+
};
432+
let opt_field_type = input.parse()?;
433+
input.parse::<Token![;]>()?;
434+
let setters = input
435+
.parse_terminated(OptionSetter::parse, Token![,])?
436+
.into_iter()
437+
.collect();
438+
Ok(Self {
439+
opt_field_name,
440+
opt_field_type,
441+
setters,
442+
})
443+
}
444+
}
445+
446+
struct OptionSetter {
447+
attrs: Vec<Attribute>,
448+
name: Ident,
449+
type_: Path,
450+
}
451+
452+
impl Parse for OptionSetter {
453+
fn parse(input: ParseStream) -> syn::Result<Self> {
454+
let attrs = input.call(Attribute::parse_outer)?;
455+
let name = input.parse()?;
456+
input.parse::<Token![:]>()?;
457+
let type_ = input.parse()?;
458+
Ok(Self { attrs, name, type_ })
459+
}
460+
}

src/action.rs

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -73,52 +73,7 @@ pub struct Single;
7373
#[allow(missing_docs)]
7474
pub struct Multiple;
7575

76-
macro_rules! option_setters {
77-
// Include options aggregate accessors.
78-
(
79-
$opt_field:ident: $opt_field_ty:ty;
80-
$(
81-
$(#[$($attrss:tt)*])*
82-
$opt_name:ident: $opt_ty:ty,
83-
)*
84-
) => {
85-
#[allow(unused)]
86-
fn options(&mut self) -> &mut $opt_field_ty {
87-
self.$opt_field.get_or_insert_with(<$opt_field_ty>::default)
88-
}
89-
90-
/// Set all options. Note that this will replace all previous values set.
91-
pub fn with_options(mut self, value: impl Into<Option<$opt_field_ty>>) -> Self {
92-
self.$opt_field = value.into();
93-
self
94-
}
95-
96-
crate::action::option_setters!($opt_field_ty;
97-
$(
98-
$(#[$($attrss)*])*
99-
$opt_name: $opt_ty,
100-
)*
101-
);
102-
};
103-
// Just generate field setters.
104-
(
105-
$opt_field_ty:ty;
106-
$(
107-
$(#[$($attrss:tt)*])*
108-
$opt_name:ident: $opt_ty:ty,
109-
)*
110-
) => {
111-
$(
112-
#[doc = concat!("Set the [`", stringify!($opt_field_ty), "::", stringify!($opt_name), "`] option.")]
113-
$(#[$($attrss)*])*
114-
pub fn $opt_name(mut self, value: $opt_ty) -> Self {
115-
self.options().$opt_name = Some(value);
116-
self
117-
}
118-
)*
119-
};
120-
}
121-
use option_setters;
76+
use action_macro::option_setters;
12277

12378
pub(crate) mod private {
12479
pub trait Sealed {}

src/action/csfle/create_data_key.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,9 @@ pub struct DataKeyOptions {
4343
}
4444

4545
impl<'a> CreateDataKey<'a> {
46-
option_setters! { options: DataKeyOptions; }
47-
48-
/// Set the [`DataKeyOptions::key_alt_names`] option.
49-
pub fn key_alt_names(mut self, value: impl IntoIterator<Item = String>) -> Self {
50-
self.options().key_alt_names = Some(value.into_iter().collect());
51-
self
52-
}
53-
54-
/// Set the [`DataKeyOptions::key_material`] option.
55-
pub fn key_material(mut self, value: impl IntoIterator<Item = u8>) -> Self {
56-
self.options().key_material = Some(value.into_iter().collect());
57-
self
46+
option_setters! { options: DataKeyOptions;
47+
key_alt_names: Vec<String>,
48+
key_material: Vec<u8>,
5849
}
5950

6051
#[cfg(test)]

0 commit comments

Comments
 (0)