Repository: Marwes/combine (language: rust)

Issue:
Allow passing fmt::Display through Info
https://gitter.im/Marwes/combine?at=5d4e6e8fc87a0963e74547df


Discussion / hints:

Fix patch:
diff --git a/benches/json.rs b/benches/json.rs
index bda9a94b..7624b73b 100644
--- a/benches/json.rs
+++ b/benches/json.rs
@@ -107,7 +107,7 @@ where
     Input::Error: ParseError<Input::Item, Input::Range, Input::Position>,
 {
     parser(|input: &mut Input| {
-        let (c, consumed) = r#try!(any().parse_lazy(input).into());
+        let (c, consumed) = any().parse_lazy(input).into_result()?;
         let mut back_slash_char = satisfy_map(|c| {
             Some(match c {
                 '"' => '"',
diff --git a/src/error.rs b/src/error.rs
index 4ff534db..18a3fac1 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -39,31 +39,171 @@ macro_rules! ctry {
     };
 }
 
+/// Trait for types which can be used to construct error information.
+///
+/// To call functions expecting this trait, use the wrappar types defined in this module
+/// `Token`, `Range`, `Format` or `Static`/`&'static str`
+pub trait ErrorInfo<'s, T, R> {
+    type Format: fmt::Display;
+    fn into_info(&'s self) -> Info<T, R, Self::Format>;
+}
+
+impl<'s, 'a, T, R, F> ErrorInfo<'s, T, R> for &'a F
+where
+    F: ErrorInfo<'s, T, R>,
+{
+    type Format = F::Format;
+    fn into_info(&'s self) -> Info<T, R, Self::Format> {
+        (**self).into_info()
+    }
+}
+
 #[derive(Clone, Debug)]
-pub enum Info<T, R> {
+pub enum Info<T, R, F = &'static str> {
     Token(T),
     Range(R),
-    Borrowed(&'static str),
+    Static(&'static str),
+    Format(F),
 }
 
-impl<R> From<char> for Info<char, R> {
-    fn from(s: char) -> Info<char, R> {
+impl<'s, T, R, F> ErrorInfo<'s, T, R> for Info<T, R, F>
+where
+    T: Clone,
+    R: Clone,
+    F: fmt::Display + 's,
+{
+    type Format = &'s F;
+    fn into_info(&'s self) -> Info<T, R, <Self as ErrorInfo<T, R>>::Format> {
+        match self {
+            Info::Token(b) => Info::Token(b.clone()),
+            Info::Range(b) => Info::Range(b.clone()),
+            Info::Static(b) => Info::Static(*b),
+            Info::Format(b) => Info::Format(b),
+        }
+    }
+}
+
+impl<R, F> From<char> for Info<char, R, F> {
+    fn from(s: char) -> Self {
         Info::Token(s)
     }
 }
 
-impl<T, R> From<&'static str> for Info<T, R> {
-    fn from(s: &'static str) -> Info<T, R> {
-        Info::Borrowed(s)
+impl<'s, R> ErrorInfo<'s, char, R> for char {
+    type Format = &'static str;
+    fn into_info(&self) -> Info<char, R, Self::Format> {
+        Info::Token(*self)
+    }
+}
+
+impl<T, R, F> From<&'static str> for Info<T, R, F> {
+    fn from(s: &'static str) -> Self {
+        Info::Static(s)
+    }
+}
+
+impl<'s, T, R> ErrorInfo<'s, T, R> for &'static str {
+    type Format = &'static str;
+    fn into_info(&self) -> Info<T, R, Self::Format> {
+        Info::Static(*self)
     }
 }
 
-impl<R> From<u8> for Info<u8, R> {
-    fn from(s: u8) -> Info<u8, R> {
+impl<R, F> From<u8> for Info<u8, R, F> {
+    fn from(s: u8) -> Self {
         Info::Token(s)
     }
 }
 
+impl<R> ErrorInfo<'_, Self, R> for u8 {
+    type Format = &'static str;
+    fn into_info(&self) -> Info<Self, R, Self::Format> {
+        Info::Token(*self)
+    }
+}
+
+/// Newtype which constructs an `Info::Token` through `ErrorInfo`
+pub struct Token<T>(pub T);
+
+impl<T, R> From<Token<T>> for Info<T, R, &'static str> {
+    fn from(s: Token<T>) -> Self {
+        Info::Token(s.0)
+    }
+}
+
+impl<'s, T, R> ErrorInfo<'s, T, R> for Token<T>
+where
+    T: Clone,
+{
+    type Format = &'static str;
+    fn into_info(&'s self) -> Info<T, R, Self::Format> {
+        Info::Token(self.0.clone())
+    }
+}
+
+/// Newtype which constructs an `Info::Range` through `ErrorInfo`
+pub struct Range<R>(pub R);
+
+impl<T, R> From<Range<R>> for Info<T, R, &'static str> {
+    fn from(s: Range<R>) -> Self {
+        Info::Range(s.0)
+    }
+}
+
+impl<'s, T, R> ErrorInfo<'s, T, R> for Range<R>
+where
+    R: Clone,
+{
+    type Format = &'static str;
+    fn into_info(&'s self) -> Info<T, R, Self::Format> {
+        Info::Range(self.0.clone())
+    }
+}
+
+/// Newtype which constructs an `Info::Static` through `ErrorInfo`
+/// A plain `&'static str` can also be used, this exists for consistency.
+pub struct Static(&'static str);
+
+impl<T, R, F> From<Static> for Info<T, R, F>
+where
+    F: fmt::Display,
+{
+    fn from(s: Static) -> Self {
+        Info::Static(s.0)
+    }
+}
+
+impl<'s, T, R> ErrorInfo<'s, T, R> for Static {
+    type Format = &'static str;
+    fn into_info(&'s self) -> Info<T, R, Self::Format> {
+        Info::Static(self.0)
+    }
+}
+
+/// Newtype which constructs an `Info::Format` through `ErrorInfo`
+pub struct Format<F>(pub F)
+where
+    F: fmt::Display;
+
+impl<T, R, F> From<Format<F>> for Info<T, R, F>
+where
+    F: fmt::Display,
+{
+    fn from(s: Format<F>) -> Self {
+        Info::Format(s.0)
+    }
+}
+
+impl<'s, T, R, F> ErrorInfo<'s, T, R> for Format<F>
+where
+    F: fmt::Display + 's,
+{
+    type Format = &'s F;
+    fn into_info(&'s self) -> Info<T, R, Self::Format> {
+        Info::Format(&self.0)
+    }
+}
+
 /// Enum used to indicate if a parser consumed any items of the stream it was given as an input.
 ///
 /// This is used by parsers such as `or` and `choice` to determine if they should try to parse
@@ -224,49 +364,61 @@ pub type StdParseResult2<O, E> = Result<(O, Consumed<()>), Consumed<Tracked<E>>>
 pub trait StreamError<Item, Range>: Sized {
     fn unexpected_token(token: Item) -> Self;
     fn unexpected_range(token: Range) -> Self;
-    fn unexpected_message<T>(msg: T) -> Self
+    fn unexpected_format<T>(msg: T) -> Self
     where
         T: fmt::Display;
-    fn unexpected(info: Info<Item, Range>) -> Self {
-        match info {
+    fn unexpected<E>(info: E) -> Self
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
+        match info.into_info() {
             Info::Token(b) => Self::unexpected_token(b),
             Info::Range(b) => Self::unexpected_range(b),
-            Info::Borrowed(b) => Self::unexpected_static_message(b),
+            Info::Static(b) => Self::unexpected_static_message(b),
+            Info::Format(b) => Self::unexpected_format(b),
         }
     }
     fn unexpected_static_message(msg: &'static str) -> Self {
-        Self::unexpected_message(msg)
+        Self::unexpected_format(msg)
     }
 
     fn expected_token(token: Item) -> Self;
     fn expected_range(token: Range) -> Self;
-    fn expected_message<T>(msg: T) -> Self
+    fn expected_format<T>(msg: T) -> Self
     where
         T: fmt::Display;
-    fn expected(info: Info<Item, Range>) -> Self {
-        match info {
+    fn expected<E>(info: E) -> Self
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
+        match info.into_info() {
             Info::Token(b) => Self::expected_token(b),
             Info::Range(b) => Self::expected_range(b),
-            Info::Borrowed(b) => Self::expected_static_message(b),
+            Info::Static(b) => Self::expected_static_message(b),
+            Info::Format(b) => Self::expected_format(b),
         }
     }
     fn expected_static_message(msg: &'static str) -> Self {
-        Self::expected_message(msg)
+        Self::expected_format(msg)
     }
 
     fn message_token(token: Item) -> Self;
     fn message_range(token: Range) -> Self;
-    fn message_message<T>(msg: T) -> Self
+    fn message_format<T>(msg: T) -> Self
     where
         T: fmt::Display;
     fn message_static_message(msg: &'static str) -> Self {
-        Self::message_message(msg)
+        Self::message_format(msg)
     }
-    fn message(info: Info<Item, Range>) -> Self {
-        match info {
+    fn message<E>(info: E) -> Self
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
+        match info.into_info() {
             Info::Token(b) => Self::message_token(b),
             Info::Range(b) => Self::message_range(b),
-            Info::Borrowed(b) => Self::message_static_message(b),
+            Info::Static(b) => Self::message_static_message(b),
+            Info::Format(b) => Self::message_format(b),
         }
     }
 
@@ -275,7 +427,7 @@ pub trait StreamError<Item, Range>: Sized {
     where
         E: StdError + Send + Sync + 'static,
     {
-        Self::message_message(err)
+        Self::message_format(err)
     }
 
     fn end_of_input() -> Self {
@@ -325,15 +477,24 @@ pub trait ParseError<Item, Range, Position>: Sized + PartialEq {
     /// it to a vector while others may only keep `self` or `err` to avoid allocation
     fn add(&mut self, err: Self::StreamError);
 
-    fn add_expected(&mut self, info: Info<Item, Range>) {
+    fn add_expected<E>(&mut self, info: E)
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
         self.add(Self::StreamError::expected(info))
     }
 
-    fn add_unexpected(&mut self, info: Info<Item, Range>) {
+    fn add_unexpected<E>(&mut self, info: E)
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
         self.add(Self::StreamError::unexpected(info))
     }
 
-    fn add_message(&mut self, info: Info<Item, Range>) {
+    fn add_message<E>(&mut self, info: E)
+    where
+        E: for<'s> ErrorInfo<'s, Item, Range>,
+    {
         self.add(Self::StreamError::message(info))
     }
 
@@ -383,7 +544,7 @@ impl<Item, Range> StreamError<Item, Range> for UnexpectedParse {
         UnexpectedParse::Unexpected
     }
     #[inline]
-    fn unexpected_message<T>(_: T) -> Self
+    fn unexpected_format<T>(_: T) -> Self
     where
         T: fmt::Display,
     {
@@ -399,14 +560,14 @@ impl<Item, Range> StreamError<Item, Range> for UnexpectedParse {
         UnexpectedParse::Unexpected
     }
     #[inline]
-    fn expected_message<T>(_: T) -> Self
+    fn expected_format<T>(_: T) -> Self
     where
         T: fmt::Display,
     {
         UnexpectedParse::Unexpected
     }
     #[inline]
-    fn message_message<T>(_: T) -> Self
+    fn message_format<T>(_: T) -> Self
     where
         T: fmt::Display,
     {
@@ -525,7 +686,7 @@ impl<Item, Range> StreamError<Item, Range> for StringStreamError {
         StringStreamError::UnexpectedParse
     }
     #[inline]
-    fn unexpected_message<T>(_msg: T) -> Self
+    fn unexpected_format<T>(_msg: T) -> Self
     where
         T: fmt::Display,
     {
@@ -541,14 +702,14 @@ impl<Item, Range> StreamError<Item, Range> for StringStreamError {
         StringStreamError::UnexpectedParse
     }
     #[inline]
-    fn expected_message<T>(_: T) -> Self
+    fn expected_format<T>(_: T) -> Self
     where
         T: fmt::Display,
     {
         StringStreamError::UnexpectedParse
     }
     #[inline]
-    fn message_message<T>(_: T) -> Self
+    fn message_format<T>(_: T) -> Self
     where
         T: fmt::Display,
     {
diff --git a/src/parser/byte.rs b/src/parser/byte.rs
index 1c77a361..7da4639f 100644
--- a/src/parser/byte.rs
+++ b/src/parser/byte.rs
@@ -6,7 +6,7 @@ use crate::lib::marker::PhantomData;
 use self::ascii::AsciiChar;
 
 use crate::combinator::{satisfy, skip_many, token, Expected, Satisfy, SkipMany, Token};
-use crate::error::{Info, ParseError, ParseResult, Tracked};
+use crate::error::{self, ParseError, ParseResult, Tracked};
 use crate::parser::{
     item::tokens_cmp,
     range::{take_fn, TakeRange},
@@ -350,7 +350,7 @@ where [
     let s = *s;
     tokens_cmp(s.iter().cloned(), cmp)
         .map(move |_| s)
-        .expected(Info::Range(s))
+        .expected(error::Range(s))
 }
 }
 
diff --git a/src/parser/combinator.rs b/src/parser/combinator.rs
index 2aca6fca..864da47f 100644
--- a/src/parser/combinator.rs
+++ b/src/parser/combinator.rs
@@ -73,7 +73,7 @@ pub fn not_followed_by<Input, P>(parser: P) -> NotFollowedBy<P>
 where
     Input: Stream,
     P: Parser<Input>,
-    P::Output: Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+    P::Output: Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range, &'static str>>,
 {
     NotFollowedBy(parser)
 }
@@ -1170,7 +1170,7 @@ where [
     parser.and_then(|r| {
         r.from_utf8()
             .map_err(|_| StreamErrorFor::<Input>::expected_static_message("UTF-8"))
-            .and_then(|s| s.parse().map_err(StreamErrorFor::<Input>::message_message))
+            .and_then(|s| s.parse().map_err(StreamErrorFor::<Input>::message_format))
     })
 }
 }
diff --git a/src/parser/error.rs b/src/parser/error.rs
index f9b65905..5c8ba058 100644
--- a/src/parser/error.rs
+++ b/src/parser/error.rs
@@ -2,19 +2,22 @@
 
 use crate::lib::marker::PhantomData;
 
-use crate::error::{Info, ParseError, ParseResult, StreamError, Tracked};
-use crate::parser::ParseMode;
-use crate::{Parser, Stream, StreamOnce};
+use crate::{
+    error::{ErrorInfo, ParseError, ParseResult, StreamError, Tracked},
+    parser::ParseMode,
+    Parser, Stream, StreamOnce,
+};
 
 use crate::error::ParseResult::*;
 
 #[derive(Clone)]
-pub struct Unexpected<I, T>(Info<I::Item, I::Range>, PhantomData<fn(I) -> (I, T)>)
+pub struct Unexpected<I, T, E>(E, PhantomData<fn(I) -> (I, T)>)
 where
     I: Stream;
-impl<Input, T> Parser<Input> for Unexpected<Input, T>
+impl<Input, T, E> Parser<Input> for Unexpected<Input, T, E>
 where
     Input: Stream,
+    E: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     type Output = T;
     type PartialState = ();
@@ -23,7 +26,7 @@ where
         EmptyErr(<Input as StreamOnce>::Error::empty(input.position()).into())
     }
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        errors.error.add(StreamError::unexpected(self.0.clone()));
+        errors.error.add(StreamError::unexpected(&self.0));
     }
 }
 /// Always fails with `message` as an unexpected error.
@@ -44,15 +47,15 @@ where
 ///         .unwrap()
 ///         .errors
 ///         .iter()
-///         .any(|m| *m == StreamError::unexpected("token".into()))
+///         .any(|m| *m == StreamError::unexpected("token"))
 /// );
 /// # }
 /// ```
 #[inline]
-pub fn unexpected<I, S>(message: S) -> Unexpected<I, ()>
+pub fn unexpected<Input, S>(message: S) -> Unexpected<Input, (), S>
 where
-    I: Stream,
-    S: Into<Info<I::Item, I::Range>>,
+    Input: Stream,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     unexpected_any(message)
 }
@@ -77,17 +80,17 @@ where
 ///         .unwrap()
 ///         .errors
 ///         .iter()
-///         .any(|m| *m == StreamError::unexpected("token".into()))
+///         .any(|m| *m == StreamError::unexpected("token"))
 /// );
 /// # }
 /// ```
 #[inline]
-pub fn unexpected_any<I, S, T>(message: S) -> Unexpected<I, T>
+pub fn unexpected_any<Input, S, T>(message: S) -> Unexpected<Input, T, S>
 where
-    I: Stream,
-    S: Into<Info<I::Item, I::Range>>,
+    Input: Stream,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
-    Unexpected(message.into(), PhantomData)
+    Unexpected(message, PhantomData)
 }
 
 #[derive(Clone)]
@@ -96,7 +99,7 @@ impl<Input, P, S> Parser<Input> for Message<P, S>
 where
     Input: Stream,
     P: Parser<Input>,
-    S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     type Output = P::Output;
     type PartialState = P::PartialState;
@@ -118,7 +121,7 @@ where
 
             // The message should always be added even if some input was consumed before failing
             ConsumedErr(mut err) => {
-                err.add_message(self.1.clone().into());
+                err.add_message(&self.1);
                 ConsumedErr(err)
             }
 
@@ -129,7 +132,7 @@ where
 
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
         self.0.add_error(errors);
-        errors.error.add_message(self.1.clone().into());
+        errors.error.add_message(&self.1);
     }
 
     forward_parser!(Input, parser_count add_consumed_expected_error, 0);
@@ -143,7 +146,7 @@ pub fn message<Input, P, S>(p: P, msg: S) -> Message<P, S>
 where
     P: Parser<Input>,
     Input: Stream,
-    S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     Message(p, msg)
 }
@@ -154,7 +157,7 @@ impl<Input, P, S> Parser<Input> for Expected<P, S>
 where
     P: Parser<Input>,
     Input: Stream,
-    S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     type Output = P::Output;
     type PartialState = P::PartialState;
@@ -174,13 +177,9 @@ where
     }
 
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        ParseError::set_expected(
-            errors,
-            StreamError::expected(self.1.clone().into()),
-            |errors| {
-                self.0.add_error(errors);
-            },
-        )
+        ParseError::set_expected(errors, StreamError::expected(&self.1), |errors| {
+            self.0.add_error(errors);
+        })
     }
 
     forward_parser!(Input, parser_count add_consumed_expected_error, 0);
@@ -194,7 +193,7 @@ pub fn expected<Input, P, S>(p: P, info: S) -> Expected<P, S>
 where
     P: Parser<Input>,
     Input: Stream,
-    S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+    S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
 {
     Expected(p, info)
 }
diff --git a/src/parser/function.rs b/src/parser/function.rs
index eba95db9..23784580 100644
--- a/src/parser/function.rs
+++ b/src/parser/function.rs
@@ -48,7 +48,7 @@ pub struct FnParser<Input, F>(F, PhantomData<fn(Input) -> Input>);
 ///         //Return an empty error since we only tested the first token of the stream
 ///         let errors = easy::Errors::new(
 ///             position,
-///             StreamError::expected(From::from("even number"))
+///             StreamError::expected("even number")
 ///         );
 ///         Err(Consumed::Empty(errors.into()))
 ///     }
diff --git a/src/parser/item.rs b/src/parser/item.rs
index 77ffa28f..bf13c129 100644
--- a/src/parser/item.rs
+++ b/src/parser/item.rs
@@ -3,7 +3,7 @@
 use crate::lib::marker::PhantomData;
 
 use crate::{
-    error::{Info, ParseError, ParseResult, ResultExt, StreamError, Tracked},
+    error::{self, ErrorInfo, ParseError, ParseResult, ResultExt, StreamError, Tracked},
     stream::{uncons, Stream, StreamOnce},
     Parser,
 };
@@ -194,7 +194,7 @@ where
         satisfy_impl(input, |c| if c == self.c { Some(c) } else { None })
     }
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        errors.error.add_expected(Info::Token(self.c.clone()));
+        errors.error.add_expected(error::Token(self.c.clone()));
     }
 }
 
@@ -223,19 +223,20 @@ where
 }
 
 #[derive(Clone)]
-pub struct Tokens<C, T, Input>
+pub struct Tokens<C, E, T, Input>
 where
     Input: Stream,
 {
     cmp: C,
-    expected: Info<Input::Item, Input::Range>,
+    expected: E,
     tokens: T,
     _marker: PhantomData<Input>,
 }
 
-impl<Input, C, T> Parser<Input> for Tokens<C, T, Input>
+impl<Input, C, E, T> Parser<Input> for Tokens<C, E, T, Input>
 where
     C: FnMut(T::Item, Input::Item) -> bool,
+    E: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
     T: Clone + IntoIterator,
     Input: Stream,
 {
@@ -252,9 +253,9 @@ where
                         return if consumed {
                             let mut errors = <Input as StreamOnce>::Error::from_error(
                                 start,
-                                StreamError::unexpected(Info::Token(other)),
+                                StreamError::unexpected_token(other),
                             );
-                            errors.add_expected(self.expected.clone());
+                            errors.add_expected(&self.expected);
                             ConsumedErr(errors)
                         } else {
                             EmptyErr(<Input as StreamOnce>::Error::empty(start).into())
@@ -283,7 +284,7 @@ where
         }
     }
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        errors.error.add_expected(self.expected.clone());
+        errors.error.add_expected(&self.expected);
     }
 }
 
@@ -296,16 +297,15 @@ where
 /// ```
 /// # extern crate combine;
 /// # use combine::*;
-/// # use combine::error::Info;
+/// # use combine::error;
 /// # fn main() {
-/// use std::ascii::AsciiExt;
-/// let result = tokens(|l, r| l.eq_ignore_ascii_case(&r), "abc".into(), "abc".chars())
+/// let result = tokens(|l, r| l.eq_ignore_ascii_case(&r), "abc", "abc".chars())
 ///     .parse("AbC")
 ///     .map(|x| x.0.as_str());
 /// assert_eq!(result, Ok("abc"));
 /// let result = tokens(
 ///     |&l, r| (if l < r { r - l } else { l - r }) <= 2,
-///     Info::Range(&b"025"[..]),
+///     error::Range(&b"025"[..]),
 ///     &b"025"[..]
 /// )
 ///     .parse(&b"123"[..])
@@ -314,20 +314,16 @@ where
 /// # }
 /// ```
 #[inline]
-pub fn tokens<C, T, Input>(
-    cmp: C,
-    expected: Info<Input::Item, Input::Range>,
-    tokens: T,
-) -> Tokens<C, T, Input>
+pub fn tokens<C, E, T, Input>(cmp: C, expected: E, tokens: T) -> Tokens<C, E, T, Input>
 where
     C: FnMut(T::Item, Input::Item) -> bool,
     T: Clone + IntoIterator,
     Input: Stream,
 {
     Tokens {
-        cmp: cmp,
-        expected: expected,
-        tokens: tokens,
+        cmp,
+        expected,
+        tokens,
         _marker: PhantomData,
     }
 }
@@ -362,7 +358,7 @@ where
                         return if consumed {
                             let errors = <Input as StreamOnce>::Error::from_error(
                                 start,
-                                StreamError::unexpected(Info::Token(other)),
+                                StreamError::unexpected_token(other),
                             );
                             ConsumedErr(errors)
                         } else {
@@ -503,7 +499,7 @@ where
 
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
         for expected in self.tokens.clone() {
-            errors.error.add_expected(Info::Token(expected));
+            errors.error.add_expected(error::Token(expected));
         }
     }
 }
@@ -649,7 +645,7 @@ where
     }
 
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        errors.error.add_expected("end of input".into());
+        errors.error.add_expected("end of input");
     }
 }
 
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index a15eadf1..0896f457 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9,7 +9,7 @@ use crate::combinator::{
     FlatMap, Iter, Map, MapInput, Message, Then, ThenPartial,
 };
 use crate::error::ParseResult::*;
-use crate::error::{Info, ParseError, ParseResult, ResultExt, Tracked};
+use crate::error::{ErrorInfo, ParseError, ParseResult, ResultExt, Token, Tracked};
 use crate::parser::error::{silent, Silent};
 use crate::stream::{Stream, StreamOnce};
 use crate::ErrorOffset;
@@ -135,7 +135,7 @@ pub trait Parser<Input: Stream> {
             ctry!(input.reset(before.clone()).consumed());
             if let Ok(t) = input.uncons() {
                 ctry!(input.reset(before).consumed());
-                error.error.add_unexpected(Info::Token(t));
+                error.error.add_unexpected(Token(t));
             }
             self.add_error(error);
         }
@@ -202,7 +202,7 @@ pub trait Parser<Input: Stream> {
             ctry!(input.reset(before.clone()).consumed());
             if let Ok(t) = input.uncons() {
                 ctry!(input.reset(before).consumed());
-                error.error.add_unexpected(Info::Token(t));
+                error.error.add_unexpected(Token(t));
             }
             self.add_error(error);
         }
@@ -607,7 +607,7 @@ pub trait Parser<Input: Stream> {
     fn message<S>(self, msg: S) -> Message<Self, S>
     where
         Self: Sized,
-        S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+        S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
     {
         message(self, msg)
     }
@@ -619,6 +619,7 @@ pub trait Parser<Input: Stream> {
     /// # #![cfg(feature = "std")]
     /// # extern crate combine;
     /// # use combine::*;
+    /// # use combine::error;
     /// # use combine::stream::easy;
     /// # use combine::stream::state::{State, SourcePosition};
     /// # fn main() {
@@ -632,12 +633,23 @@ pub trait Parser<Input: Stream> {
     ///         easy::Error::Expected("nine".into())
     ///     ]
     /// }));
+    ///
+    /// let result = token('9')
+    ///     .expected(error::Format(format_args!("That is not a nine!")))
+    ///     .easy_parse(State::new("8"));
+    /// assert_eq!(result, Err(easy::Errors {
+    ///     position: SourcePosition::default(),
+    ///     errors: vec![
+    ///         easy::Error::Unexpected('8'.into()),
+    ///         easy::Error::Expected("That is not a nine!".to_string().into())
+    ///     ]
+    /// }));
     /// # }
     /// ```
     fn expected<S>(self, msg: S) -> Expected<Self, S>
     where
         Self: Sized,
-        S: Clone + Into<Info<<Input as StreamOnce>::Item, <Input as StreamOnce>::Range>>,
+        S: for<'s> ErrorInfo<'s, Input::Item, Input::Range>,
     {
         expected(self, msg)
     }
@@ -1032,7 +1044,7 @@ pub trait ParseMode: Copy {
             ctry!(input.reset(before.clone()).consumed());
             if let Ok(t) = input.uncons() {
                 ctry!(input.reset(before).consumed());
-                error.error.add_unexpected(Info::Token(t));
+                error.error.add_unexpected(Token(t));
             }
             parser.add_error(error);
         }
diff --git a/src/parser/range.rs b/src/parser/range.rs
index 1702da62..74eeaaba 100644
--- a/src/parser/range.rs
+++ b/src/parser/range.rs
@@ -8,7 +8,7 @@
 use crate::lib::marker::PhantomData;
 
 use crate::error::ParseResult::*;
-use crate::error::{Info, ParseError, ParseResult, ResultExt, StreamError, Tracked};
+use crate::error::{self, ParseError, ParseResult, ResultExt, StreamError, Tracked};
 use crate::parser::ParseMode;
 use crate::stream::{
     uncons_range, uncons_while, uncons_while1, wrap_stream_error, FullRangeStream,
@@ -48,7 +48,7 @@ where
     }
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
         // TODO Add unexpected message?
-        errors.error.add_expected(Info::Range(self.0.clone()));
+        errors.error.add_expected(error::Range(self.0.clone()));
     }
 }
 
diff --git a/src/parser/regex.rs b/src/parser/regex.rs
index e8d00fdd..4b4a055e 100644
--- a/src/parser/regex.rs
+++ b/src/parser/regex.rs
@@ -239,7 +239,7 @@ where
         }
     }
     fn add_error(&mut self, error: &mut Tracked<<Input as StreamOnce>::Error>) {
-        error.error.add(StreamError::expected_message(format_args!(
+        error.error.add(StreamError::expected_format(format_args!(
             "/{}/",
             self.0.as_str()
         )))
@@ -296,7 +296,7 @@ where
         }
     }
     fn add_error(&mut self, error: &mut Tracked<<Input as StreamOnce>::Error>) {
-        error.error.add(StreamError::expected_message(format_args!(
+        error.error.add(StreamError::expected_format(format_args!(
             "/{}/",
             self.0.as_str()
         )))
@@ -355,7 +355,7 @@ where
         take(end).parse_lazy(input).map(|_| value)
     }
     fn add_error(&mut self, error: &mut Tracked<<Input as StreamOnce>::Error>) {
-        error.error.add(StreamError::expected_message(format_args!(
+        error.error.add(StreamError::expected_format(format_args!(
             "/{}/",
             self.0.as_str()
         )))
@@ -416,7 +416,7 @@ where
         }
     }
     fn add_error(&mut self, error: &mut Tracked<<Input as StreamOnce>::Error>) {
-        error.error.add(StreamError::expected_message(format_args!(
+        error.error.add(StreamError::expected_format(format_args!(
             "/{}/",
             self.0.as_str()
         )))
@@ -482,7 +482,7 @@ where
         take(end).parse_lazy(input).map(|_| value)
     }
     fn add_error(&mut self, error: &mut Tracked<<Input as StreamOnce>::Error>) {
-        error.error.add(StreamError::expected_message(format_args!(
+        error.error.add(StreamError::expected_format(format_args!(
             "/{}/",
             self.0.as_str()
         )))
diff --git a/src/parser/repeat.rs b/src/parser/repeat.rs
index 2224ef87..8536def6 100644
--- a/src/parser/repeat.rs
+++ b/src/parser/repeat.rs
@@ -103,7 +103,7 @@ where
                 .inspect(|_| *count += 1),
         );
         if *count < self.min {
-            let err = StreamError::message_message(format_args!(
+            let err = StreamError::message_format(format_args!(
                 "expected {} more elements",
                 self.min - *count
             ));
@@ -1395,10 +1395,10 @@ where
     }
 
     fn add_error(&mut self, errors: &mut Tracked<<Input as StreamOnce>::Error>) {
-        use crate::error::Info;
+        use crate::error;
         self.parser.add_error(errors);
 
-        errors.error.add_expected(Info::Token(self.escape.clone()));
+        errors.error.add_expected(error::Token(self.escape.clone()));
     }
 }
 
diff --git a/src/stream/easy.rs b/src/stream/easy.rs
index 19933ade..c3e61ed8 100644
--- a/src/stream/easy.rs
+++ b/src/stream/easy.rs
@@ -34,7 +34,7 @@
 //!                 if word == "combine" {
 //!                     Ok(word)
 //!                 } else {
-//!                     Err(easy::Error::Expected(easy::Info::Borrowed("combine")))
+//!                     Err(easy::Error::Expected(easy::Info::Static("combine")))
 //!                 }
 //!             })
 //!         }
@@ -100,15 +100,19 @@ pub enum Info<T, R> {
     Token(T),
     Range(R),
     Owned(String),
-    Borrowed(&'static str),
+    Static(&'static str),
 }
 
-impl<T, R> From<PrimitiveInfo<T, R>> for Info<T, R> {
-    fn from(info: PrimitiveInfo<T, R>) -> Self {
+impl<T, R, F> From<PrimitiveInfo<T, R, F>> for Info<T, R>
+where
+    F: fmt::Display,
+{
+    fn from(info: PrimitiveInfo<T, R, F>) -> Self {
         match info {
             PrimitiveInfo::Token(b) => Info::Token(b),
             PrimitiveInfo::Range(b) => Info::Range(b),
-            PrimitiveInfo::Borrowed(b) => Info::Borrowed(b),
+            PrimitiveInfo::Static(b) => Info::Static(b),
+            PrimitiveInfo::Format(b) => Info::Owned(b.to_string()),
         }
     }
 }
@@ -123,7 +127,7 @@ impl<T, R> Info<T, R> {
             Token(t) => Token(f(t)),
             Range(r) => Range(r),
             Owned(s) => Owned(s),
-            Borrowed(x) => Borrowed(x),
+            Static(x) => Static(x),
         }
     }
 
@@ -136,7 +140,7 @@ impl<T, R> Info<T, R> {
             Token(t) => Token(t),
             Range(r) => Range(f(r)),
             Owned(s) => Owned(s),
-            Borrowed(x) => Borrowed(x),
+            Static(x) => Static(x),
         }
     }
 }
@@ -147,9 +151,9 @@ impl<T: PartialEq, R: PartialEq> PartialEq for Info<T, R> {
             (&Info::Token(ref l), &Info::Token(ref r)) => l == r,
             (&Info::Range(ref l), &Info::Range(ref r)) => l == r,
             (&Info::Owned(ref l), &Info::Owned(ref r)) => l == r,
-            (&Info::Borrowed(l), &Info::Owned(ref r)) => l == r,
-            (&Info::Owned(ref l), &Info::Borrowed(r)) => l == r,
-            (&Info::Borrowed(l), &Info::Borrowed(r)) => l == r,
+            (&Info::Static(l), &Info::Owned(ref r)) => l == r,
+            (&Info::Owned(ref l), &Info::Static(r)) => l == r,
+            (&Info::Static(l), &Info::Static(r)) => l == r,
             _ => false,
         }
     }
@@ -160,7 +164,7 @@ impl<T: fmt::Display, R: fmt::Display> fmt::Display for Info<T, R> {
             Info::Token(ref c) => write!(f, "{}", c),
             Info::Range(ref c) => write!(f, "{}", c),
             Info::Owned(ref s) => write!(f, "{}", s),
-            Info::Borrowed(s) => write!(f, "{}", s),
+            Info::Static(s) => write!(f, "{}", s),
         }
     }
 }
@@ -178,7 +182,7 @@ impl<T, R> From<String> for Info<T, R> {
 
 impl<T, R> From<&'static str> for Info<T, R> {
     fn from(s: &'static str) -> Info<T, R> {
-        Info::Borrowed(s)
+        Info::Static(s)
     }
 }
 
@@ -215,7 +219,7 @@ where
         Error::Unexpected(Info::Range(token))
     }
     #[inline]
-    fn unexpected_message<T>(msg: T) -> Self
+    fn unexpected_format<T>(msg: T) -> Self
     where
         T: fmt::Display,
     {
@@ -223,7 +227,7 @@ where
     }
     #[inline]
     fn unexpected_static_message(msg: &'static str) -> Self {
-        Error::Unexpected(Info::Borrowed(msg))
+        Error::Unexpected(Info::Static(msg))
     }
 
     #[inline]
@@ -235,7 +239,7 @@ where
         Error::Expected(Info::Range(token))
     }
     #[inline]
-    fn expected_message<T>(msg: T) -> Self
+    fn expected_format<T>(msg: T) -> Self
     where
         T: fmt::Display,
     {
@@ -243,11 +247,11 @@ where
     }
     #[inline]
     fn expected_static_message(msg: &'static str) -> Self {
-        Error::Expected(Info::Borrowed(msg))
+        Error::Expected(Info::Static(msg))
     }
 
     #[inline]
-    fn message_message<T>(msg: T) -> Self
+    fn message_format<T>(msg: T) -> Self
     where
         T: fmt::Display,
     {
@@ -255,7 +259,7 @@ where
     }
     #[inline]
     fn message_static_message(msg: &'static str) -> Self {
-        Error::Message(Info::Borrowed(msg))
+        Error::Message(Info::Static(msg))
     }
     #[inline]
     fn message_token(token: Item) -> Self {
@@ -287,22 +291,22 @@ where
             Error::Unexpected(info) => match info {
                 Info::Token(x) => T::unexpected_token(x),
                 Info::Range(x) => T::unexpected_range(x),
-                Info::Borrowed(x) => T::unexpected_static_message(x),
-                Info::Owned(x) => T::unexpected_message(x),
+                Info::Static(x) => T::unexpected_static_message(x),
+                Info::Owned(x) => T::unexpected_format(x),
             },
             Error::Expected(info) => match info {
                 Info::Token(x) => T::expected_token(x),
                 Info::Range(x) => T::expected_range(x),
-                Info::Borrowed(x) => T::expected_static_message(x),
-                Info::Owned(x) => T::expected_message(x),
+                Info::Static(x) => T::expected_static_message(x),
+                Info::Owned(x) => T::expected_format(x),
             },
             Error::Message(info) => match info {
                 Info::Token(x) => T::expected_token(x),
                 Info::Range(x) => T::expected_range(x),
-                Info::Borrowed(x) => T::expected_static_message(x),
-                Info::Owned(x) => T::expected_message(x),
+                Info::Static(x) => T::expected_static_message(x),
+                Info::Owned(x) => T::expected_format(x),
             },
-            Error::Other(err) => T::message_message(err),
+            Error::Other(err) => T::message_format(err),
         }
     }
 }
diff --git a/travis.sh b/travis.sh
index 573f8583..aeca4242 100755
--- a/travis.sh
+++ b/travis.sh
@@ -5,8 +5,7 @@ cargo build
 cargo test --features doc
 cargo test --features doc --examples
 
-cargo check --bench json
-cargo check --bench http
+cargo test --bench json --bench http -- --test
 cargo check --bench mp4 --features mp4
 
 cargo build --no-default-features


Test patch: