Skip to content

Commit

Permalink
Require user to provide version to methods
Browse files Browse the repository at this point in the history
This takes advantage of post-monomorphization errors to statically
validate the version passed.
  • Loading branch information
jhpratt committed Feb 16, 2023
1 parent c45264c commit 9615d56
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 69 deletions.
82 changes: 42 additions & 40 deletions tests/parse_format_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ use time::format_description::{self, Component, FormatItem, OwnedFormatItem};

#[test]
fn empty() {
assert_eq!(format_description::parse(""), Ok(vec![]));
assert_eq!(format_description::parse_borrowed::<2>(""), Ok(vec![]));
assert_eq!(
format_description::parse_owned(""),
format_description::parse_owned::<2>(""),
Ok(OwnedFormatItem::Compound(Box::new([])))
);
}
Expand Down Expand Up @@ -288,7 +288,7 @@ fn errors() {
Err($error) $(if $condition)?
));
assert!(matches!(
format_description::parse_owned($format_description),
format_description::parse_owned::<2>($format_description),
Err($error) $(if $condition)?
));
)*};
Expand Down Expand Up @@ -458,7 +458,7 @@ fn component_with_modifiers() {
#[test]
fn optional() {
assert_eq!(
format_description::parse_owned("[optional [:[year]]]"),
format_description::parse_owned::<2>("[optional [:[year]]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Compound(Box::new([
OwnedFormatItem::Literal(Box::new(*b":")),
Expand All @@ -467,19 +467,19 @@ fn optional() {
)))
);
assert_eq!(
format_description::parse_owned("[optional [[year]]]"),
format_description::parse_owned::<2>("[optional [[year]]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Component(Component::Year(Default::default()))
)))
);
assert_eq!(
format_description::parse_owned(r"[optional [\[]]"),
format_description::parse_owned::<2>(r"[optional [\[]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Literal(Box::new(*b"["))
)))
);
assert_eq!(
format_description::parse_owned(r"[optional [ \[ ]]"),
format_description::parse_owned::<2>(r"[optional [ \[ ]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Compound(Box::new([
OwnedFormatItem::Literal(Box::new(*b" ")),
Expand All @@ -493,34 +493,34 @@ fn optional() {
#[test]
fn first() {
assert_eq!(
format_description::parse_owned("[first [a]]"),
format_description::parse_owned::<2>("[first [a]]"),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Literal(Box::new(*b"a"))
])))
);
assert_eq!(
format_description::parse_owned("[first [a] [b]]"),
format_description::parse_owned::<2>("[first [a] [b]]"),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Literal(Box::new(*b"a")),
OwnedFormatItem::Literal(Box::new(*b"b")),
])))
);
assert_eq!(
format_description::parse_owned("[first [a][b]]"),
format_description::parse_owned::<2>("[first [a][b]]"),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Literal(Box::new(*b"a")),
OwnedFormatItem::Literal(Box::new(*b"b")),
])))
);
assert_eq!(
format_description::parse_owned(r"[first [a][\[]]"),
format_description::parse_owned::<2>(r"[first [a][\[]]"),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Literal(Box::new(*b"a")),
OwnedFormatItem::Literal(Box::new(*b"[")),
])))
);
assert_eq!(
format_description::parse_owned(r"[first [a][\[\[]]"),
format_description::parse_owned::<2>(r"[first [a][\[\[]]"),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Literal(Box::new(*b"a")),
OwnedFormatItem::Compound(Box::new([
Expand All @@ -530,7 +530,9 @@ fn first() {
])))
);
assert_eq!(
format_description::parse_owned("[first [[period case:upper]] [[period case:lower]] ]"),
format_description::parse_owned::<2>(
"[first [[period case:upper]] [[period case:lower]] ]"
),
Ok(OwnedFormatItem::First(Box::new([
OwnedFormatItem::Component(Component::Period(modifier!(Period {
is_uppercase: true,
Expand All @@ -547,56 +549,56 @@ fn first() {
#[test]
fn backslash_escape() {
assert_eq!(
format_description::parse_owned(r"[optional [\]]]"),
format_description::parse_owned::<2>(r"[optional [\]]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Literal(Box::new(*b"]"))
)))
);
assert_eq!(
format_description::parse_owned(r"[optional [\[]]"),
format_description::parse_owned::<2>(r"[optional [\[]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Literal(Box::new(*b"["))
)))
);
assert_eq!(
format_description::parse_owned(r"[optional [\\]]"),
format_description::parse_owned::<2>(r"[optional [\\]]"),
Ok(OwnedFormatItem::Optional(Box::new(
OwnedFormatItem::Literal(Box::new(*br"\"))
)))
);
assert_eq!(
format_description::parse_owned(r"\\"),
format_description::parse_owned::<2>(r"\\"),
Ok(OwnedFormatItem::Literal(Box::new(*br"\")))
);
assert_eq!(
format_description::parse_owned(r"\["),
format_description::parse_owned::<2>(r"\["),
Ok(OwnedFormatItem::Literal(Box::new(*br"[")))
);
assert_eq!(
format_description::parse_owned(r"\]"),
format_description::parse_owned::<2>(r"\]"),
Ok(OwnedFormatItem::Literal(Box::new(*br"]")))
);
assert_eq!(
format_description::parse_owned(r"foo\\"),
format_description::parse_owned::<2>(r"foo\\"),
Ok(OwnedFormatItem::Compound(Box::new([
OwnedFormatItem::Literal(Box::new(*b"foo")),
OwnedFormatItem::Literal(Box::new(*br"\")),
])))
);
assert_eq!(
format_description::parse_borrowed(r"\\"),
format_description::parse_borrowed::<2>(r"\\"),
Ok(vec![FormatItem::Literal(br"\")])
);
assert_eq!(
format_description::parse_borrowed(r"\["),
format_description::parse_borrowed::<2>(r"\["),
Ok(vec![FormatItem::Literal(br"[")])
);
assert_eq!(
format_description::parse_borrowed(r"\]"),
format_description::parse_borrowed::<2>(r"\]"),
Ok(vec![FormatItem::Literal(br"]")])
);
assert_eq!(
format_description::parse_borrowed(r"foo\\"),
format_description::parse_borrowed::<2>(r"foo\\"),
Ok(vec![
FormatItem::Literal(b"foo"),
FormatItem::Literal(br"\"),
Expand All @@ -607,31 +609,31 @@ fn backslash_escape() {
#[test]
fn backslash_escape_error() {
assert!(matches!(
format_description::parse_owned(r"\a"),
format_description::parse_owned::<2>(r"\a"),
Err(InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: 1,
..
})
));
assert!(matches!(
format_description::parse_owned(r"\"),
format_description::parse_owned::<2>(r"\"),
Err(InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: 0,
..
})
));
assert!(matches!(
format_description::parse_borrowed(r"\a"),
format_description::parse_borrowed::<2>(r"\a"),
Err(InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: 1,
..
})
));
assert!(matches!(
format_description::parse_borrowed(r"\"),
format_description::parse_borrowed::<2>(r"\"),
Err(InvalidFormatDescription::Expected {
what: "valid escape sequence",
index: 0,
Expand All @@ -643,15 +645,15 @@ fn backslash_escape_error() {
#[test]
fn nested_v1_error() {
assert!(matches!(
format_description::parse_owned("[optional [[[]]"),
format_description::parse_owned::<2>("[optional [[[]]"),
Err(InvalidFormatDescription::MissingComponentName { index: 11, .. })
));
assert!(matches!(
format_description::parse_owned("[optional [ [[ ]]"),
format_description::parse_owned::<2>("[optional [ [[ ]]"),
Err(InvalidFormatDescription::MissingComponentName { index: 12, .. })
));
assert!(matches!(
format_description::parse_owned("[first [a][[[]]"),
format_description::parse_owned::<2>("[first [a][[[]]"),
Err(InvalidFormatDescription::UnclosedOpeningBracket { index: 0, .. })
));
}
Expand Down Expand Up @@ -679,43 +681,43 @@ fn nested_error() {
})
));
assert!(matches!(
format_description::parse_owned("[year [month]]"),
format_description::parse_owned::<2>("[year [month]]"),
Err(InvalidModifier { value, index: 6, .. }) if value == "["
));
assert!(matches!(
format_description::parse_owned("[optional[]]"),
format_description::parse_owned::<2>("[optional[]]"),
Err(Expected {
what: "whitespace after `optional`",
index: 8,
..
})
));
assert!(matches!(
format_description::parse_owned("[first[]]"),
format_description::parse_owned::<2>("[first[]]"),
Err(Expected {
what: "whitespace after `first`",
index: 5,
..
})
));
assert!(matches!(
format_description::parse_owned("[optional []"),
format_description::parse_owned::<2>("[optional []"),
Err(UnclosedOpeningBracket { index: 0, .. })
));
assert!(matches!(
format_description::parse_owned("[first []"),
format_description::parse_owned::<2>("[first []"),
Err(UnclosedOpeningBracket { index: 0, .. })
));
assert!(matches!(
format_description::parse_owned("[optional ["),
format_description::parse_owned::<2>("[optional ["),
Err(UnclosedOpeningBracket { index: 10, .. })
));
assert!(matches!(
format_description::parse_owned("[optional [[year"),
format_description::parse_owned::<2>("[optional [[year"),
Err(UnclosedOpeningBracket { index: 11, .. })
));
assert!(matches!(
format_description::parse_owned("[optional "),
format_description::parse_owned::<2>("[optional "),
Err(Expected {
what: "opening bracket",
index: 9,
Expand Down Expand Up @@ -749,7 +751,7 @@ fn error_display() {
"missing component name at byte index 0"
);
assert_eq!(
format_description::parse_owned("[optional ")
format_description::parse_owned::<2>("[optional ")
.unwrap_err()
.to_string(),
"expected opening bracket at byte index 9"
Expand Down
7 changes: 5 additions & 2 deletions tests/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,13 +699,16 @@ fn parse_time_err() -> time::Result<()> {
))
));
assert!(matches!(
Time::parse("1a", &fd::parse_owned("[subsecond digits:2]")?),
Time::parse("1a", &fd::parse_owned::<2>("[subsecond digits:2]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("1a", [fd::parse_owned("[subsecond digits:2]")?].as_slice()),
Time::parse(
"1a",
[fd::parse_owned::<2>("[subsecond digits:2]")?].as_slice()
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
Expand Down
3 changes: 1 addition & 2 deletions time/src/format_description/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
//!
//! The formatted value will be output to the provided writer. Format descriptions can be
//! [well-known](crate::format_description::well_known) or obtained by using the
//! [`format_description!`](crate::macros::format_description) macro, the
//! [`format_description::parse`](crate::format_description::parse()) function.
//! [`format_description!`](crate::macros::format_description) macro or a function listed below.

mod borrowed_format_item;
mod component;
Expand Down
17 changes: 12 additions & 5 deletions time/src/format_description/parse/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ pub(super) fn parse<
'item: 'iter,
'iter,
I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
const VERSION: u8,
const VERSION: usize,
>(
tokens: &'iter mut lexer::Lexed<I>,
) -> impl Iterator<Item = Result<Item<'item>, Error>> + 'iter {
assert!(version!(1..=2));
validate_version!(VERSION);
parse_inner::<_, false, VERSION>(tokens)
}

Expand All @@ -112,10 +112,11 @@ fn parse_inner<
'item,
I: Iterator<Item = Result<lexer::Token<'item>, Error>>,
const NESTED: bool,
const VERSION: u8,
const VERSION: usize,
>(
tokens: &mut lexer::Lexed<I>,
) -> impl Iterator<Item = Result<Item<'item>, Error>> + '_ {
validate_version!(VERSION);
iter::from_fn(move || {
if NESTED && tokens.peek_closing_bracket().is_some() {
return None;
Expand Down Expand Up @@ -177,10 +178,15 @@ fn parse_inner<
}

/// Parse a component. This assumes that the opening bracket has already been consumed.
fn parse_component<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: u8>(
fn parse_component<
'a,
I: Iterator<Item = Result<lexer::Token<'a>, Error>>,
const VERSION: usize,
>(
opening_bracket: Location,
tokens: &mut lexer::Lexed<I>,
) -> Result<Item<'a>, Error> {
validate_version!(VERSION);
let leading_whitespace = tokens.next_if_whitespace();

guard!(let Some(name) = tokens.next_if_not_whitespace() else {
Expand Down Expand Up @@ -346,10 +352,11 @@ fn parse_component<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, cons
}

/// Parse a nested format description. The location provided is the the most recent one consumed.
fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: u8>(
fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: usize>(
last_location: Location,
tokens: &mut lexer::Lexed<I>,
) -> Result<NestedFormatDescription<'a>, Error> {
validate_version!(VERSION);
guard!(let Some(opening_bracket) = tokens.next_if_opening_bracket() else {
return Err(Error {
_inner: unused(last_location.error("expected opening bracket")),
Expand Down
8 changes: 4 additions & 4 deletions time/src/format_description/parse/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ fn attach_location<'item>(
/// `VERSION` controls the version of the format description that is being parsed. Currently, this
/// must be 1 or 2.
///
/// - When `VERSION` is 0, `[[` is the only escape sequence, resulting in a literal `[`.
/// - When `VERSION` is 1, all escape sequences begin with `\`. The only characters that may
/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`.
/// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may
/// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All
/// other characters result in a lex error.
pub(super) fn lex<const VERSION: u8>(
pub(super) fn lex<const VERSION: usize>(
mut input: &[u8],
) -> Lexed<impl Iterator<Item = Result<Token<'_>, Error>>> {
assert!(version!(1..=2));
validate_version!(VERSION);

let mut depth: u8 = 0;
let mut iter = attach_location(input.iter()).peekable();
Expand Down
Loading

0 comments on commit 9615d56

Please sign in to comment.