Skip to content

Commit b2b602a

Browse files
committed
Permit visibility in serde::format_description!
1 parent e3dcbd3 commit b2b602a

File tree

3 files changed

+83
-55
lines changed

3 files changed

+83
-55
lines changed

tests/serde/macros.rs

+21-15
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,24 @@ time::serde::format_description!(
2020
Iso8601::<{ iso8601::Config::DEFAULT.encode() }>
2121
);
2222

23-
serde::format_description!(
24-
offset_dt_format,
25-
OffsetDateTime,
26-
"custom format: [year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
27-
sign:mandatory]:[offset_minute]"
28-
);
29-
serde::format_description!(
30-
primitive_dt_format,
31-
PrimitiveDateTime,
32-
"custom format: [year]-[month]-[day] [hour]:[minute]:[second]"
33-
);
34-
serde::format_description!(time_format, Time, "custom format: [minute]:[second]");
23+
mod nested {
24+
time::serde::format_description!(
25+
pub(super) offset_dt_format,
26+
OffsetDateTime,
27+
"custom format: [year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
28+
sign:mandatory]:[offset_minute]"
29+
);
30+
time::serde::format_description!(
31+
pub primitive_dt_format,
32+
PrimitiveDateTime,
33+
"custom format: [year]-[month]-[day] [hour]:[minute]:[second]"
34+
);
35+
time::serde::format_description!(
36+
pub(in crate::serde::macros) time_format,
37+
Time,
38+
"custom format: [minute]:[second]"
39+
);
40+
}
3541
serde::format_description!(date_format, Date, "custom format: [year]-[month]-[day]");
3642
serde::format_description!(
3743
offset_format,
@@ -50,13 +56,13 @@ serde::format_description!(
5056

5157
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
5258
struct TestCustomFormat {
53-
#[serde(with = "offset_dt_format")]
59+
#[serde(with = "nested::offset_dt_format")]
5460
offset_dt: OffsetDateTime,
55-
#[serde(with = "primitive_dt_format::option")]
61+
#[serde(with = "nested::primitive_dt_format::option")]
5662
primitive_dt: Option<PrimitiveDateTime>,
5763
#[serde(with = "date_format")]
5864
date: Date,
59-
#[serde(with = "time_format::option")]
65+
#[serde(with = "nested::time_format::option")]
6066
time: Option<Time>,
6167
#[serde(with = "offset_format")]
6268
offset: UtcOffset,

time-macros/src/lib.rs

+55-37
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ mod utc_datetime;
3434
#[cfg(any(feature = "formatting", feature = "parsing"))]
3535
use std::iter::Peekable;
3636

37+
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
38+
use proc_macro::Delimiter;
3739
use proc_macro::TokenStream;
3840
#[cfg(any(feature = "formatting", feature = "parsing"))]
39-
use proc_macro::{Ident, TokenTree};
41+
use proc_macro::TokenTree;
4042

4143
use self::error::Error;
4244

@@ -60,34 +62,38 @@ macro_rules! impl_macros {
6062

6163
impl_macros![date datetime utc_datetime offset time];
6264

65+
#[cfg(any(feature = "formatting", feature = "parsing"))]
66+
type PeekableTokenStreamIter = Peekable<proc_macro::token_stream::IntoIter>;
67+
6368
#[cfg(any(feature = "formatting", feature = "parsing"))]
6469
enum FormatDescriptionVersion {
6570
V1,
6671
V2,
6772
}
6873

69-
#[cfg(any(feature = "formatting", feature = "parsing"))]
70-
enum VersionOrModuleName {
71-
Version(FormatDescriptionVersion),
72-
#[cfg_attr(not(feature = "serde"), allow(dead_code))]
73-
ModuleName(Ident),
74-
}
75-
7674
#[cfg(any(feature = "formatting", feature = "parsing"))]
7775
fn parse_format_description_version<const NO_EQUALS_IS_MOD_NAME: bool>(
78-
iter: &mut Peekable<proc_macro::token_stream::IntoIter>,
79-
) -> Result<Option<VersionOrModuleName>, Error> {
80-
let version_ident = match iter.peek() {
81-
Some(TokenTree::Ident(ident)) if ident.to_string() == "version" => match iter.next() {
82-
Some(TokenTree::Ident(ident)) => ident,
83-
_ => unreachable!(),
84-
},
76+
iter: &mut PeekableTokenStreamIter,
77+
) -> Result<Option<FormatDescriptionVersion>, Error> {
78+
let version_ident = match iter.peek().ok_or(Error::UnexpectedEndOfInput)? {
79+
version @ TokenTree::Ident(ident) if ident.to_string() == "version" => {
80+
let version_ident = version.clone();
81+
iter.next(); // consume `version`
82+
version_ident
83+
}
8584
_ => return Ok(None),
8685
};
86+
8787
match iter.peek() {
8888
Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => iter.next(),
8989
_ if NO_EQUALS_IS_MOD_NAME => {
90-
return Ok(Some(VersionOrModuleName::ModuleName(version_ident)));
90+
// Push the `version` ident to the front of the iterator.
91+
*iter = std::iter::once(version_ident)
92+
.chain(iter.clone())
93+
.collect::<TokenStream>()
94+
.into_iter()
95+
.peekable();
96+
return Ok(None);
9197
}
9298
Some(token) => {
9399
return Err(Error::Custom {
@@ -134,20 +140,37 @@ fn parse_format_description_version<const NO_EQUALS_IS_MOD_NAME: bool>(
134140
};
135141
helpers::consume_punct(',', iter)?;
136142

137-
Ok(Some(VersionOrModuleName::Version(version)))
143+
Ok(Some(version))
144+
}
145+
146+
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
147+
fn parse_visibility(iter: &mut PeekableTokenStreamIter) -> Result<TokenStream, Error> {
148+
let mut visibility = match iter.peek().ok_or(Error::UnexpectedEndOfInput)? {
149+
pub_ident @ TokenTree::Ident(ident) if ident.to_string() == "pub" => {
150+
let visibility = quote! { #(pub_ident.clone()) };
151+
iter.next(); // consume `pub`
152+
visibility
153+
}
154+
_ => return Ok(quote! {}),
155+
};
156+
157+
match iter.peek().ok_or(Error::UnexpectedEndOfInput)? {
158+
group @ TokenTree::Group(path) if path.delimiter() == Delimiter::Parenthesis => {
159+
visibility.extend(std::iter::once(group.clone()));
160+
iter.next(); // consume parentheses and path
161+
}
162+
_ => {}
163+
}
164+
165+
Ok(visibility)
138166
}
139167

140168
#[cfg(any(feature = "formatting", feature = "parsing"))]
141169
#[proc_macro]
142170
pub fn format_description(input: TokenStream) -> TokenStream {
143171
(|| {
144172
let mut input = input.into_iter().peekable();
145-
let version = match parse_format_description_version::<false>(&mut input)? {
146-
Some(VersionOrModuleName::Version(version)) => Some(version),
147-
None => None,
148-
// This branch should never occur here, as `false` is the provided as a const parameter.
149-
Some(VersionOrModuleName::ModuleName(_)) => bug!("branch should never occur"),
150-
};
173+
let version = parse_format_description_version::<false>(&mut input)?;
151174
let (span, string) = helpers::get_string_literal(input)?;
152175
let items = format_description::parse_with_version(version, &string, span)?;
153176

@@ -172,22 +195,16 @@ pub fn serde_format_description(input: TokenStream) -> TokenStream {
172195

173196
// First, the optional format description version.
174197
let version = parse_format_description_version::<true>(&mut tokens)?;
175-
let (version, mod_name) = match version {
176-
Some(VersionOrModuleName::ModuleName(module_name)) => (None, Some(module_name)),
177-
Some(VersionOrModuleName::Version(version)) => (Some(version), None),
178-
None => (None, None),
179-
};
198+
199+
// Then, the visibility of the module.
200+
let visibility = parse_visibility(&mut tokens)?;
180201

181202
// Next, an identifier (the desired module name)
182-
// Only parse this if it wasn't parsed when attempting to get the version.
183-
let mod_name = match mod_name {
184-
Some(mod_name) => mod_name,
185-
None => match tokens.next() {
186-
Some(TokenTree::Ident(ident)) => Ok(ident),
187-
Some(tree) => Err(Error::UnexpectedToken { tree }),
188-
None => Err(Error::UnexpectedEndOfInput),
189-
}?,
190-
};
203+
let mod_name = match tokens.next() {
204+
Some(TokenTree::Ident(ident)) => Ok(ident),
205+
Some(tree) => Err(Error::UnexpectedToken { tree }),
206+
None => Err(Error::UnexpectedEndOfInput),
207+
}?;
191208

192209
// Followed by a comma
193210
helpers::consume_punct(',', &mut tokens)?;
@@ -230,6 +247,7 @@ pub fn serde_format_description(input: TokenStream) -> TokenStream {
230247
};
231248

232249
Ok(serde_format_description::build(
250+
visibility,
233251
mod_name,
234252
formattable,
235253
format,

time-macros/src/serde_format_description.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use proc_macro::{Ident, TokenStream, TokenTree};
22

33
pub(crate) fn build(
4+
visibility: TokenStream,
45
mod_name: Ident,
56
ty: TokenTree,
67
format: TokenStream,
@@ -150,9 +151,10 @@ pub(crate) fn build(
150151
};
151152

152153
quote! {
153-
mod #(mod_name) {
154+
#S(visibility) mod #(mod_name) {
154155
use super::*;
155-
// TODO Remove the prefix, forcing the user to import the type themself.
156+
// TODO Remove the prefix, forcing the user to import the type themself. This must be
157+
// done in a breaking change.
156158
use ::time::#(ty) as __TimeSerdeType;
157159

158160
const fn description() -> impl #S(fd_traits) {
@@ -163,7 +165,9 @@ pub(crate) fn build(
163165
#S(serialize_primary)
164166
#S(deserialize_primary)
165167

166-
pub(super) mod option {
168+
// While technically public, this is effectively the same visibility as the enclosing
169+
// module, which has its visibility controlled by the user.
170+
pub mod option {
167171
use super::{description, __TimeSerdeType};
168172
#S(deserialize_option_imports)
169173

0 commit comments

Comments
 (0)