Repository: pest-parser/pest (rust)

Issue:
Owned variant of errors
It'd be nice to have an owned Error type, when bubbling up errors is needed. Ideally, the owned version would have all the `&str`s in the Spans/Positions into shared `Arc<String>`s instead.

Pull request description:
Refactored Error.
The former `Error` type kept reference to the input. This meant that it was impossible for an Error to be fully owned, resulting in a hard-to-use type that was incompatible with failure. This commit Refactors the error code, implements a friendlier API, promotes error to a public mod, and transforms `Error` into an owned type. Fixes #204.

Patch:
diff --git a/derive/src/generator.rs b/derive/src/generator.rs
index 8b83110..b97bfa5 100644
--- a/derive/src/generator.rs
+++ b/derive/src/generator.rs
@@ -44,7 +44,7 @@ pub fn generate(
                 input: &'i str
             ) -> ::std::result::Result<
                 ::pest::iterators::Pairs<'i, Rule>,
-                ::pest::Error<'i, Rule>
+                ::pest::error::Error<Rule>
             > {
                 mod rules {
                     use super::Rule;
@@ -899,7 +899,7 @@ mod tests {
                     input: &'i str
                 ) -> ::std::result::Result<
                     ::pest::iterators::Pairs<'i, Rule>,
-                    ::pest::Error<'i, Rule>
+                    ::pest::error::Error<Rule>
                 > {
                     mod rules {
                         use super::Rule;
diff --git a/pest/examples/parens.rs b/pest/examples/parens.rs
index fd28d91..d6bc158 100644
--- a/pest/examples/parens.rs
+++ b/pest/examples/parens.rs
@@ -2,7 +2,8 @@ extern crate pest;
 
 use std::io::{self, Write};
 
-use pest::{state, Error, ParseResult, Parser, ParserState};
+use pest::{state, ParseResult, Parser, ParserState};
+use pest::error::Error;
 use pest::iterators::Pairs;
 
 #[allow(dead_code, non_camel_case_types)]
diff --git a/pest/src/error.rs b/pest/src/error.rs
index 9a827a2..1f82290 100644
--- a/pest/src/error.rs
+++ b/pest/src/error.rs
@@ -7,6 +7,9 @@
 // option. All files in the project carrying such notice may not be copied,
 // modified, or distributed except according to those terms.
 
+//! A `mod` containing error data structures.
+
+use std::cmp;
 use std::error;
 use std::fmt;
 use std::mem;
@@ -15,35 +18,73 @@ use RuleType;
 use position::Position;
 use span::Span;
 
-/// An `enum` which defines possible errors.
+/// A `struct` defining errors.
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub struct Error<R> {
+    /// Variant of the error
+    pub variant: ErrorVariant<R>,
+    pub location: InputLocation,
+    line: String,
+    continued_line: Option<String>,
+    start: (usize, usize),
+    end: Option<(usize, usize)>
+}
+
+/// An `enum` describing `Error` variants.
 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
-pub enum Error<'i, R> {
-    /// Generated parsing error with expected and unexpected `Rule`s and a position
+pub enum ErrorVariant<R> {
+    /// Generated parsing error with expected and unexpected `Rule`s
     ParsingError {
         /// Positive attempts
         positives: Vec<R>,
         /// Negative attempts
-        negatives: Vec<R>,
-        /// Deepest position of attempts
-        pos: Position<'i>
-    },
-    /// Custom error with a message and a position
-    CustomErrorPos {
-        /// Short explanation
-        message: String,
-        /// Error `Position` for formatting
-        pos: Position<'i>
+        negatives: Vec<R>
     },
-    /// Custom error with a message and a span defined by a start and end position
-    CustomErrorSpan {
+    /// Custom error with a message
+    CustomError {
         /// Short explanation
-        message: String,
-        /// Error `Span` for formatting
-        span: Span<'i>
+        message: String
     }
 }
 
-impl<'i, R: RuleType> Error<'i, R> {
+/// An `enum` describing where the `Error` has occurred.
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub enum InputLocation {
+    /// `Error` was created by `Error::new_from_pos`
+    Pos(usize),
+    /// `Error` was created by `Error::new_from_span`
+    Span((usize, usize))
+}
+
+impl<R: RuleType> Error<R> {
+    pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position) -> Error<R> {
+        Error {
+            variant,
+            location: InputLocation::Pos(pos.pos()),
+            line: pos.line_of().to_owned(),
+            continued_line: None,
+            start: pos.line_col(),
+            end: None
+        }
+    }
+
+    pub fn new_from_span(variant: ErrorVariant<R>, span: Span) -> Error<R> {
+        let continued_line = if span.start_pos().line_col().0 != span.end_pos().line_col().0 {
+            Some(span.end_pos().line_of().to_owned())
+        } else {
+            None
+        };
+
+        Error {
+            variant,
+            location: InputLocation::Span((span.start(), span.end())),
+            line: span.start_pos().line_of().to_owned(),
+            continued_line,
+            start: span.start_pos().line_col(),
+            end: Some(span.end_pos().line_col())
+        }
+    }
+
     /// Renames all `Rule`s from a `ParsingError` variant returning a `CustomErrorPos`. It does
     /// nothing when called on `CustomErrorPos` and `CustomErrorSpan` variants.
     ///
@@ -52,7 +93,7 @@ impl<'i, R: RuleType> Error<'i, R> {
     /// # Examples
     ///
     /// ```
-    /// # use pest::Error;
+    /// # use pest::error::{Error, ErrorVariant};
     /// # use pest::Position;
     /// # #[allow(non_camel_case_types)]
     /// # #[allow(dead_code)]
@@ -63,188 +104,217 @@ impl<'i, R: RuleType> Error<'i, R> {
     /// # }
     /// # let input = "";
     /// # let pos = Position::from_start(input);
-    /// Error::ParsingError {
-    ///     positives: vec![Rule::open_paren],
-    ///     negatives: vec![Rule::closed_paren],
-    ///     pos: pos
-    /// }.renamed_rules(|rule| {
+    /// Error::new_from_pos(
+    ///     ErrorVariant::ParsingError {
+    ///         positives: vec![Rule::open_paren],
+    ///         negatives: vec![Rule::closed_paren]
+    ///     },
+    ///     pos
+    /// ).renamed_rules(|rule| {
     ///     match *rule {
     ///         Rule::open_paren => "(".to_owned(),
     ///         Rule::closed_paren => "closed paren".to_owned()
     ///     }
     /// });
     /// ```
-    pub fn renamed_rules<F>(self, f: F) -> Error<'i, R>
+    pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
     where
         F: FnMut(&R) -> String
     {
-        match self {
-            Error::ParsingError {
+        let variant = match self.variant {
+            ErrorVariant::ParsingError {
                 positives,
-                negatives,
-                pos
+                negatives
             } => {
-                let message = parsing_error_message(&positives, &negatives, f);
-                Error::CustomErrorPos { message, pos }
+                let message = Error::parsing_error_message(&positives, &negatives, f);
+                ErrorVariant::CustomError { message }
             }
-            error => error
-        }
-    }
-}
-
-fn message<'i, R: fmt::Debug>(error: &Error<'i, R>) -> String {
-    match *error {
-        Error::ParsingError {
-            ref positives,
-            ref negatives,
-            ..
-        } => parsing_error_message(positives, negatives, |r| format!("{:?}", r)),
-        Error::CustomErrorPos { ref message, .. } | Error::CustomErrorSpan { ref message, .. } => {
-            message.to_owned()
-        }
-    }
-}
+            variant => variant
+        };
 
-fn parsing_error_message<R: fmt::Debug, F>(positives: &[R], negatives: &[R], mut f: F) -> String
-where
-    F: FnMut(&R) -> String
-{
-    match (negatives.is_empty(), positives.is_empty()) {
-        (false, false) => format!(
-            "unexpected {}; expected {}",
-            enumerate(negatives, &mut f),
-            enumerate(positives, &mut f)
-        ),
-        (false, true) => format!("unexpected {}", enumerate(negatives, &mut f)),
-        (true, false) => format!("expected {}", enumerate(positives, &mut f)),
-        (true, true) => "unknown parsing error".to_owned()
-    }
-}
+        self.variant = variant;
 
-fn enumerate<R: fmt::Debug, F>(rules: &[R], f: &mut F) -> String
-where
-    F: FnMut(&R) -> String
-{
-    match rules.len() {
-        1 => f(&rules[0]),
-        2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
-        l => {
-            let separated = rules
-                .iter()
-                .take(l - 1)
-                .map(|r| f(r))
-                .collect::<Vec<_>>()
-                .join(", ");
-            format!("{}, or {}", separated, f(&rules[l - 1]))
-        }
+        self
     }
-}
 
-fn underline(start: usize, end: Option<usize>, offset: usize) -> String {
-    let mut underline = String::new();
+    fn spacing(&self) -> String {
+        let line = if let Some((line, _)) = self.end {
+            cmp::max(self.start.0, line)
+        } else {
+            self.start.0
+        };
 
-    for _ in 0..offset {
-        underline.push(' ');
-    }
+        let line_str_len = format!("{}", line).len();
 
-    if let Some(end) = end {
-        if end - start > 1 {
-            underline.push('^');
-            for _ in 2..(end - start) {
-                underline.push('-');
-            }
-            underline.push('^');
-        } else {
-            underline.push('^');
+        let mut spacing = String::new();
+        for _ in 0..line_str_len {
+            spacing.push(' ');
         }
-    } else {
-        underline.push_str("^---")
-    }
 
-    underline
-}
-
-fn format<'i, R: fmt::Debug>(error: &Error<'i, R>) -> String {
-    let pos = match *error {
-        Error::ParsingError { ref pos, .. } | Error::CustomErrorPos { ref pos, .. } => pos.clone(),
-        Error::CustomErrorSpan { ref span, .. } => span.clone().split().0.clone()
-    };
-    let (line, col) = pos.line_col();
-    let line_str_len = format!("{}", line).len();
-
-    let mut spacing = String::new();
-    for _ in 0..line_str_len {
-        spacing.push(' ');
+        spacing
     }
 
-    let mut result = format!("{}--> {}:{}\n", spacing, line, col);
-    result.push_str(&format!("{} |\n", spacing));
+    fn underline(&self) -> String {
+        let mut underline = String::new();
 
-    let underlined;
+        let mut start = self.start.1;
+        let end = if let Some((_, mut end)) = self.end {
+            let inverted_cols = start > end;
+            if inverted_cols {
+                mem::swap(&mut start, &mut end);
+                start -= 1;
+                end += 1;
+            }
 
-    if let Error::CustomErrorSpan { ref span, .. } = *error {
-        let mut start = span.start_pos().line_col();
-        let mut end = span.end_pos().line_col();
+            Some(end)
+        } else {
+            None
+        };
+        let offset = start - 1;
 
-        if start.1 > end.1 {
-            mem::swap(&mut start.1, &mut end.1);
-            start.1 -= 1;
-            end.1 += 1;
+        for _ in 0..offset {
+            underline.push(' ');
         }
 
-        underlined = underline(start.1, Some(end.1), start.1 - 1);
-
-        if start.0 != end.0 {
-            result.push_str(&format!("{} | ", start.0));
-
-            let pos = span.start_pos();
-            let line = pos.line_of();
-            result.push_str(&format!("{}\n", line));
-
-            if end.0 - start.0 > 1 {
-                result.push_str(&format!("{} | ...\n", spacing));
+        if let Some(end) = end {
+            if end - start > 1 {
+                underline.push('^');
+                for _ in 2..(end - start) {
+                    underline.push('-');
+                }
+                underline.push('^');
+            } else {
+                underline.push('^');
             }
+        } else {
+            underline.push_str("^---")
+        }
 
-            result.push_str(&format!("{} | ", end.0));
-
-            let pos = span.end_pos();
-            let line = pos.line_of();
-            result.push_str(&format!("{}\n", line));
-            result.push_str(&format!("{} | {}\n", spacing, underlined));
-
-            result.push_str(&format!("{} |\n", spacing));
-            result.push_str(&format!("{} = {}", spacing, message(error)));
+        underline
+    }
 
-            return result;
+    fn message(&self) -> String {
+        match self.variant {
+            ErrorVariant::ParsingError {
+                ref positives,
+                ref negatives
+            } => Error::parsing_error_message(positives, negatives, |r| format!("{:?}", r)),
+            ErrorVariant::CustomError { ref message } => {
+                message.clone()
+            }
         }
-    } else {
-        underlined = underline(pos.pos(), None, col - 1);
     }
 
-    result.push_str(&format!("{} | ", line));
-
-    let line = pos.line_of();
-    result.push_str(&format!("{}\n", line));
-    result.push_str(&format!("{} | {}\n", spacing, underlined));
+    fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
+    where
+        F: FnMut(&R) -> String
+    {
+        match (negatives.is_empty(), positives.is_empty()) {
+            (false, false) => format!(
+                "unexpected {}; expected {}",
+                Error::enumerate(negatives, &mut f),
+                Error::enumerate(positives, &mut f)
+            ),
+            (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
+            (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
+            (true, true) => "unknown parsing error".to_owned()
+        }
+    }
 
-    result.push_str(&format!("{} |\n", spacing));
-    result.push_str(&format!("{} = {}", spacing, message(error)));
+    fn enumerate<F>(rules: &[R], f: &mut F) -> String
+    where
+        F: FnMut(&R) -> String
+    {
+        match rules.len() {
+            1 => f(&rules[0]),
+            2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
+            l => {
+                let separated = rules
+                    .iter()
+                    .take(l - 1)
+                    .map(|r| f(r))
+                    .collect::<Vec<_>>()
+                    .join(", ");
+                format!("{}, or {}", separated, f(&rules[l - 1]))
+            }
+        }
+    }
 
-    result
+    pub(crate) fn format(&self) -> String {
+        let spacing = self.spacing();
+
+        if let (Some(end), &Some(ref continued_line)) = (self.end, &self.continued_line) {
+            let has_line_gap = end.0 - self.start.0 > 1;
+            if has_line_gap {
+                format!(
+                    "{s    }--> {ls}:{c}\n\
+                     {s    } |\n\
+                     {ls:w$} | {line}\n\
+                     {s    } | ...\n\
+                     {le:w$} | {continued_line}\n\
+                     {s    } | {underline}\n\
+                     {s    } |\n\
+                     {s    } = {message}",
+                    s = spacing,
+                    w = spacing.len(),
+                    ls = self.start.0,
+                    le = end.0,
+                    c = self.start.1,
+                    line = self.line,
+                    continued_line = continued_line,
+                    underline = self.underline(),
+                    message = self.message()
+                )
+            } else {
+                format!(
+                    "{s    }--> {ls}:{c}\n\
+                     {s    } |\n\
+                     {ls:w$} | {line}\n\
+                     {le:w$} | {continued_line}\n\
+                     {s    } | {underline}\n\
+                     {s    } |\n\
+                     {s    } = {message}",
+                    s = spacing,
+                    w = spacing.len(),
+                    ls = self.start.0,
+                    le = end.0,
+                    c = self.start.1,
+                    line = self.line,
+                    continued_line = continued_line,
+                    underline = self.underline(),
+                    message = self.message()
+                )
+            }
+        } else {
+            format!(
+                "{s}--> {l}:{c}\n\
+                 {s} |\n\
+                 {l} | {line}\n\
+                 {s} | {underline}\n\
+                 {s} |\n\
+                 {s} = {message}",
+                s = spacing,
+                l = self.start.0,
+                c = self.start.1,
+                line = self.line,
+                underline = self.underline(),
+                message = self.message()
+            )
+        }
+    }
 }
 
-impl<'i, R: fmt::Debug> fmt::Display for Error<'i, R> {
+impl<R: RuleType> fmt::Display for Error<R> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", format(self))
+        write!(f, "{}", self.format())
     }
 }
 
-impl<'i, R: fmt::Debug> error::Error for Error<'i, R> {
+impl<'i, R: RuleType> error::Error for Error<R> {
     fn description(&self) -> &str {
-        match *self {
-            Error::ParsingError { .. } => "parsing error",
-            Error::CustomErrorPos { ref message, .. }
-            | Error::CustomErrorSpan { ref message, .. } => message
+        match self.variant {
+            ErrorVariant::ParsingError { .. } => "parsing error",
+            ErrorVariant::CustomError { ref message } => message
         }
     }
 }
@@ -258,11 +328,13 @@ mod tests {
     fn display_parsing_error_mixed() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<u32> = Error::ParsingError {
-            positives: vec![1, 2, 3],
-            negatives: vec![4, 5, 6],
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::ParsingError {
+                positives: vec![1, 2, 3],
+                negatives: vec![4, 5, 6]
+            },
             pos
-        };
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -281,11 +353,13 @@ mod tests {
     fn display_parsing_error_positives() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<u32> = Error::ParsingError {
-            positives: vec![1, 2],
-            negatives: vec![],
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::ParsingError {
+                positives: vec![1, 2],
+                negatives: vec![]
+            },
             pos
-        };
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -304,11 +378,13 @@ mod tests {
     fn display_parsing_error_negatives() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<u32> = Error::ParsingError {
-            positives: vec![],
-            negatives: vec![4, 5, 6],
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::ParsingError {
+                positives: vec![],
+                negatives: vec![4, 5, 6]
+            },
             pos
-        };
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -327,11 +403,13 @@ mod tests {
     fn display_parsing_error_unknown() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<u32> = Error::ParsingError {
-            positives: vec![],
-            negatives: vec![],
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::ParsingError {
+                positives: vec![],
+                negatives: vec![]
+            },
             pos
-        };
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -350,10 +428,12 @@ mod tests {
     fn display_custom_pos() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<&str> = Error::CustomErrorPos {
-            message: "error: big one".to_owned(),
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::CustomError {
+                message: "error: big one".to_owned()
+            },
             pos
-        };
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -373,10 +453,12 @@ mod tests {
         let input = b"ab\ncd\nefgh";
         let start = unsafe { position::new(input, 4) };
         let end = unsafe { position::new(input, 9) };
-        let error: Error<&str> = Error::CustomErrorSpan {
-            message: "error: big one".to_owned(),
-            span: start.span(&end)
-        };
+        let error: Error<u32> = Error::new_from_span(
+            ErrorVariant::CustomError {
+                message: "error: big one".to_owned()
+            },
+            start.span(&end)
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -397,10 +479,12 @@ mod tests {
         let input = b"ab\ncd\nefgh";
         let start = unsafe { position::new(input, 1) };
         let end = unsafe { position::new(input, 9) };
-        let error: Error<&str> = Error::CustomErrorSpan {
-            message: "error: big one".to_owned(),
-            span: start.span(&end)
-        };
+        let error: Error<u32> = Error::new_from_span(
+            ErrorVariant::CustomError {
+                message: "error: big one".to_owned()
+            },
+            start.span(&end)
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -418,14 +502,16 @@ mod tests {
     }
 
     #[test]
-    fn display_custom_span_two_lines_inverted() {
+    fn display_custom_span_two_lines_inverted_cols() {
         let input = b"abcdef\ngh";
         let start = unsafe { position::new(input, 5) };
         let end = unsafe { position::new(input, 8) };
-        let error: Error<&str> = Error::CustomErrorSpan {
-            message: "error: big one".to_owned(),
-            span: start.span(&end)
-        };
+        let error: Error<u32> = Error::new_from_span(
+            ErrorVariant::CustomError {
+                message: "error: big one".to_owned()
+            },
+            start.span(&end)
+        );
 
         assert_eq!(
             format!("{}", error),
@@ -445,11 +531,13 @@ mod tests {
     fn mapped_parsing_error() {
         let input = b"ab\ncd\nef";
         let pos = unsafe { position::new(input, 4) };
-        let error: Error<u32> = Error::ParsingError {
-            positives: vec![1, 2, 3],
-            negatives: vec![4, 5, 6],
+        let error: Error<u32> = Error::new_from_pos(
+            ErrorVariant::ParsingError {
+                positives: vec![1, 2, 3],
+                negatives: vec![4, 5, 6]
+            },
             pos
-        }.renamed_rules(|n| format!("{}", n + 1));
+        ).renamed_rules(|n| format!("{}", n + 1));
 
         assert_eq!(
             format!("{}", error),
diff --git a/pest/src/lib.rs b/pest/src/lib.rs
index e735ffc..279e6d7 100644
--- a/pest/src/lib.rs
+++ b/pest/src/lib.rs
@@ -56,7 +56,6 @@
 
 #![doc(html_root_url = "https://docs.rs/pest")]
 
-pub use error::Error;
 pub use parser::Parser;
 pub use parser_state::{state, Atomicity, Lookahead, ParseResult, ParserState};
 pub use position::Position;
@@ -65,7 +64,7 @@ use std::fmt::Debug;
 use std::hash::Hash;
 pub use token::Token;
 
-mod error;
+pub mod error;
 pub mod iterators;
 mod macros;
 mod parser;
diff --git a/pest/src/macros.rs b/pest/src/macros.rs
index d8ba857..3136e7d 100644
--- a/pest/src/macros.rs
+++ b/pest/src/macros.rs
@@ -160,7 +160,8 @@ macro_rules! consumes_to {
 /// ```
 /// # #[macro_use]
 /// # extern crate pest;
-/// # use pest::{Error, Parser};
+/// # use pest::Parser;
+/// # use pest::error::Error;
 /// # use pest::iterators::Pairs;
 /// # fn main() {
 /// # #[allow(non_camel_case_types)]
@@ -174,7 +175,7 @@ macro_rules! consumes_to {
 /// # struct AbcParser;
 /// #
 /// # impl Parser<Rule> for AbcParser {
-/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<'i, Rule>> {
+/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
 /// #         pest::state(input, |state| {
 /// #             state.rule(Rule::a, |state| {
 /// #                 state.skip(1).unwrap().rule(Rule::b, |state| {
@@ -260,7 +261,8 @@ macro_rules! parses_to {
 /// ```
 /// # #[macro_use]
 /// # extern crate pest;
-/// # use pest::{Error, Parser};
+/// # use pest::Parser;
+/// # use pest::error::Error;
 /// # use pest::iterators::Pairs;
 /// # fn main() {
 /// # #[allow(non_camel_case_types)]
@@ -274,7 +276,7 @@ macro_rules! parses_to {
 /// # struct AbcParser;
 /// #
 /// # impl Parser<Rule> for AbcParser {
-/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<'i, Rule>> {
+/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
 /// #         pest::state(input, |state| {
 /// #             state.rule(Rule::a, |state| {
 /// #                 state.skip(1).unwrap().rule(Rule::b, |s| {
@@ -309,14 +311,18 @@ macro_rules! fails_with {
 
             let error = $parser::parse($rules::$rule, $string).unwrap_err();
 
-            match error {
-                $crate::Error::ParsingError { positives, negatives, pos } => {
+            match error.variant {
+                $crate::error::ErrorVariant::ParsingError { positives, negatives } => {
                     assert_eq!(positives, $positives);
                     assert_eq!(negatives, $negatives);
-                    assert_eq!(pos.pos(), $pos);
                 }
                 _ => unreachable!()
             };
+
+            match error.location {
+                $crate::error::InputLocation::Pos(pos) => assert_eq!(pos, $pos),
+                _ => unreachable!()
+            }
         }
     };
 }
@@ -338,7 +344,7 @@ pub mod tests {
     pub struct AbcParser;
 
     impl Parser<Rule> for AbcParser {
-        fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<'i, Rule>> {
+        fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
             state(input, |state| {
                 state
                     .rule(Rule::a, |s| {
diff --git a/pest/src/parser_state.rs b/pest/src/parser_state.rs
index 542ba79..21e289f 100644
--- a/pest/src/parser_state.rs
+++ b/pest/src/parser_state.rs
@@ -11,7 +11,7 @@ use std::ops::Range;
 use std::rc::Rc;
 
 use RuleType;
-use error::Error;
+use error::{Error, ErrorVariant};
 use iterators::{pairs, QueueableToken};
 use position::{self, Position};
 use span::Span;
@@ -58,7 +58,7 @@ pub struct ParserState<'i, R: RuleType> {
 /// let input = "";
 /// pest::state::<(), _>(input, |s| Ok(s)).unwrap();
 /// ```
-pub fn state<'i, R: RuleType, F>(input: &'i str, f: F) -> Result<pairs::Pairs<'i, R>, Error<'i, R>>
+pub fn state<'i, R: RuleType, F>(input: &'i str, f: F) -> Result<pairs::Pairs<'i, R>, Error<R>>
 where
     F: FnOnce(Box<ParserState<'i, R>>) -> ParseResult<Box<ParserState<'i, R>>>
 {
@@ -75,12 +75,13 @@ where
             state.neg_attempts.sort();
             state.neg_attempts.dedup();
 
-            Err(Error::ParsingError {
-                positives: state.pos_attempts.clone(),
-                negatives: state.neg_attempts.clone(),
-                // All attempted positions were legal.
-                pos: unsafe { position::new(input.as_bytes(), state.attempt_pos) }
-            })
+            Err(Error::new_from_pos(
+                ErrorVariant::ParsingError {
+                    positives: state.pos_attempts.clone(),
+                    negatives: state.neg_attempts.clone()
+                },
+                unsafe { position::new(input.as_bytes(), state.attempt_pos) }
+            ))
         }
     }
 }
diff --git a/vm/src/lib.rs b/vm/src/lib.rs
index 9993cb0..5af5e1e 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -10,7 +10,8 @@
 extern crate pest;
 extern crate pest_meta;
 
-use pest::{Atomicity, Error, ParseResult, ParserState};
+use pest::{Atomicity, ParseResult, ParserState};
+use pest::error::Error;
 use pest::iterators::Pairs;
 use pest_meta::ast::RuleType;
 use pest_meta::optimizer::{OptimizedExpr, OptimizedRule};
@@ -33,7 +34,7 @@ impl Vm {
         &'a self,
         rule: &'a str,
         input: &'i str
-    ) -> Result<Pairs<'i, &str>, Error<'i, &str>> {
+    ) -> Result<Pairs<'i, &str>, Error<&str>> {
         pest::state(input, |state| self.parse_rule(rule, state))
     }
 
@@ -191,4 +192,4 @@ impl Vm {
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/vm/src/macros.rs b/vm/src/macros.rs
index 80a7b08..a0433e4 100644
--- a/vm/src/macros.rs
+++ b/vm/src/macros.rs
@@ -196,17 +196,21 @@ macro_rules! fails_with {
             let vm = $parser;
             let error = vm.parse($rule, $string).unwrap_err();
 
-            match error {
-                ::pest::Error::ParsingError { positives, negatives, pos } => {
+            match error.variant {
+                ::pest::error::ErrorVariant::ParsingError { positives, negatives } => {
                     let positives: Vec<&str> = $positives;
                     let negatives: Vec<&str> = $negatives;
 
                     assert_eq!(positives, positives);
                     assert_eq!(negatives, negatives);
-                    assert_eq!(pos.pos(), $pos);
                 }
                 _ => unreachable!()
             };
+
+            match error.location {
+                ::pest::error::InputLocation::Pos(pos) => assert_eq!(pos, $pos),
+                _ => unreachable!()
+            }
         }
     };
 }


Test patch:
diff --git a/pest/tests/calculator.rs b/pest/tests/calculator.rs
index 864546d..f872d00 100644
--- a/pest/tests/calculator.rs
+++ b/pest/tests/calculator.rs
@@ -10,7 +10,8 @@
 #[macro_use]
 extern crate pest;
 
-use pest::{state, Error, ParseResult, Parser, ParserState};
+use pest::{state, ParseResult, Parser, ParserState};
+use pest::error::Error;
 use pest::iterators::{Pair, Pairs};
 use pest::prec_climber::{Assoc, Operator, PrecClimber};
 
diff --git a/pest/tests/json.rs b/pest/tests/json.rs
index e961682..cdd5fb8 100644
--- a/pest/tests/json.rs
+++ b/pest/tests/json.rs
@@ -10,11 +10,12 @@
 #[macro_use]
 extern crate pest;
 
-use pest::{state, Error, ParseResult, Parser, ParserState};
-use pest::Span;
-use pest::iterators::{Pair, Pairs};
 use std::collections::HashMap;
 
+use pest::{state, ParseResult, Parser, ParserState, Span};
+use pest::error::Error;
+use pest::iterators::{Pair, Pairs};
+
 #[allow(dead_code, non_camel_case_types)]
 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 enum Rule {