Skip to content

Potential changes to nom error handling story for 6.0 #1136

@yaahc

Description

@yaahc

I haven't tested this stuff out yet so I'm not sure how feasible any of it is but here are the ideas I think are worth investigating for 6.0:

Move off of nom::error::ParseError and use std::error::Error instead

  • see if instead of append and add_context combinators that need to merge errors can instead use std::ops::Add (remove ParseError::add_context #1131).
    • I'm unsure of what the difference between append and or is in practice, so this may instead apply to or.
  • use From instead of from_error_kind for converting from a root cause error to a user provided error type
    • this could possibly involve introducing a new root cause error type that functionally replaces the (I, ErrorKind) tuple.

Inter-operate with foreign error types instead of discarding

  • change map_res to try to use From into the user Error type from the error type of the closure, specifically make sure to support preserving std ParseIntError and similar errors
  • change verify to work with Result instead of bool to allow custom error messages upon verify failures.

Look into interoperating with error reporting mechanisms that require 'static

Probably with the ToOwned trait in std, I'm not sure how easy it would be to write the impl for this as a user in practice.

Alternatively: we can look into removing the lifetime bound on errors that keep the input with them, my understanding is that the main reason that these carry around references is to do offsets against the original reference, we could instead carry a ptr and require that the original reference is provided when printing the final error report to get the nicely rendered contexts.

Stop treating context as Errors

The specific location in a program / context of what it was doing is not an error, or indicative of a failure, and I don't think it should be stored in the error chain/tree the same way the various parse failures are. Instead I'd like to see it gathered in a similar way to how I'm proposing we support Error Return Traces

Additional Comments

I'm not positive is now is the best time to apply these changes. I'm currently working on this RFC rust-lang/rfcs#2895 to add a way to get custom context out of dyn Errors, and a few of the changes I'm imagining would probably want to use this functionality as part of the error reporting mechanism, though it gets a little hairy because nom errors like to have references in them, and the context extraction mechanisms would work for any members with lifetimes in them...

The vague idea im throwing around in my head is something like this.

/// Assuming the error has been made owned
///
/// if the input references in leaf errors are stored entirely for the offset, could we instead
/// store ptrs?
///
/// using println here to be lazy, would actually use `std::fmt` machinery
fn example_nom_error_report(input: &I, error: &dyn std::error::Error + 'static) {
    let mut cur = Some(error);
    let mut ind = 0;

    eprintln!("Error:");
    while let Some(error) = cur {
        eprintln!("    {}: {}", ind, error);

        if let Some(causes) = error.context::<[nom::error::Error]>() {
            eprintln!("    Various parse failures:");
            for (ind, cause) in causes.iter().enumerate() {
                eprintln!("        {}: {}", ind, cause.print_with(input));
            }
        }

        if let Some(contexts) = error.context::<[nom::error::Context]>() {
            eprintln!("    Various contexts:");
            for (ind, context) in contexts.iter().enumerate() {
                eprintln!("        {}: {}", ind, context.print_with(input));
            }
        }

        cur = error.source();
        ind += 1;
    }
}

This would hopefully support printing all these errors

Error:
    0: Unable to parse Type from Input
       Various parse failures:
           0: at line 2:
             "c": { 1"hello" : "world"
                    ^
           expected '}', found 1
       Various contexts:
           1: at line 2, in map:
             "c": { 1"hello" : "world"
                  ^

           2: at line 0, in map:
             { "a" : 42,

Error:
    0: Unable to parse Type from Input
    1: invalid digit found in string

Error:
    0: Unable to parse Type from Input
       Various contexts:
           1: at line 2, in map:
             "c": { 1"hello" : "world"
                  ^

           2: at line 0, in map:
             { "a" : 42,
    1: failed verify for some specific reason

The specific syntax and error report shape are not really the point here, this would probably need a lot of tweeking, but hopefully this gets the idea across.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions