[Solved] Rust combine type signature

  1. The following code compiles:
use super::*;


use combine::error::UnexpectedParse;
use combine::parser::char::digit;
use combine::parser::choice::optional;
use combine::parser::item::item;
use combine::parser::range::recognize;
use combine::parser::repeat::{skip_many, skip_many1};
use combine::Parser;
use combine_language::LanguageEnv;
use combine_language::LanguageDef;
use combine_language::Identifier;
use combine::char::letter;
use combine::char::alpha_num;
use combine::satisfy;
use combine::char::string;
use crate::mma::data::Expr;
use combine::parser::combinator::Recognize;
use combine::parser::combinator::AndThen;

mod read;

fn main() {
    let mut parser = recognize((
        skip_many1(digit()),
        optional((item('.'), skip_many(digit())))))
        .and_then( str_to_bignum);
    let result = parser.parse("123");
    // println!("result: {:?}", result);
}


pub fn str_to_bignum(s: &str) -> Result<Expr, combine::error::StringStreamError> {
    let v: Vec<&str> = s.split('.').collect();
    let start = v[0];
    let bint = BInt::new_from_u8(start.as_bytes())
        .ok_or(combine::error::StringStreamError::UnexpectedParse)?;
    if v.len() == 1 { return Ok(Expr::BInt(bint)); }

    let dec = v[1];
    if dec.len() == 0 { return Ok(Expr::BInt(bint)); }

    return Ok(Expr::BFloat(bint.attach_real(dec.as_bytes()).unwrap()))
}
  1. Now, if we comment out the "let result = parser.parse("123")` line, then we get the following error:
use super::*;


use combine::error::UnexpectedParse;
use combine::parser::char::digit;
use combine::parser::choice::optional;
use combine::parser::item::item;
use combine::parser::range::recognize;
use combine::parser::repeat::{skip_many, skip_many1};
use combine::Parser;
use combine_language::LanguageEnv;
use combine_language::LanguageDef;
use combine_language::Identifier;
use combine::char::letter;
use combine::char::alpha_num;
use combine::satisfy;
use combine::char::string;
use crate::mma::data::Expr;
use combine::parser::combinator::Recognize;
use combine::parser::combinator::AndThen;

mod read;

fn main() {
    let mut parser = recognize((
        skip_many1(digit()),
        optional((item('.'), skip_many(digit())))))
        .and_then( str_to_bignum);
    // let result = parser.parse("123");
    // println!("result: {:?}", result);
}


pub fn str_to_bignum(s: &str) -> Result<Expr, combine::error::StringStreamError> {
    let v: Vec<&str> = s.split('.').collect();
    let start = v[0];
    let bint = BInt::new_from_u8(start.as_bytes())
        .ok_or(combine::error::StringStreamError::UnexpectedParse)?;
    if v.len() == 1 { return Ok(Expr::BInt(bint)); }

    let dec = v[1];
    if dec.len() == 0 { return Ok(Expr::BInt(bint)); }

    return Ok(Expr::BFloat(bint.attach_real(dec.as_bytes()).unwrap()))
}

Error:

error[E0284]: type annotations required: cannot resolve `<_ as combine::StreamOnce>::Error == _`

   |
25 |     let mut parser = recognize((
   |                      ^^^^^^^^^

error: aborting due to previous error

  1. My question: so I need to add a type signature to "parser" .. what type signature do I add?

Without the call to parse it is incomplete. i.e. there are multiple possible input types.

The bad choice is the exact copy of what you have from the working case;
(Note only using part that can compile in vscode.)

   let mut _parser: combine::parser::range::Recognize<(combine::parser::repeat::SkipMany1<combine::parser::char::Digit<&str>>, combine::parser::choice::Optional<(combine::parser::item::Token<&str>, combine::parser::repeat::SkipMany<combine::parser::char::Digit<&str>>)>)>
     = recognize((
            skip_many1(digit()),
            optional((item('.'), skip_many(digit())))));

Turbofish deep in code much shorter;

skip_many1(digit::<&str>()),

Alternatively with no cost is to specify relevant trait afterwards;

let _: &dyn Parser<Input = &str, Output = _, PartialState = _> = &parser;
1 Like

This is similar to this case:

fn main() {
    let v = Vec::new();
}

You can't tell what the type of v is because there is no type, while this:

fn main() {
    let mut v = Vec::new();
    v.push(());
}

you can clearly infer (And so can the compiler) that v is of type Vec<()>

1 Like

@OptimisticPeach , @jonh : Thanks! Issue fixed now.

Was there a systematic way by which:

  1. You derived the full type for
_parser: combine::parser::range::Recognize<(combine::parser::repeat::SkipMany1<combine::parser::char::Digit<&str>>, combine::parser::choice::Optional<(combine::parser::item::Token<&str>, combine::parser::repeat::SkipMany<combine::parser::char::Digit<&str>>)>)>
  1. Came up with the shorter solution of digit::<&str>() ?

===

Given my current level of Rust, I knew that the solution could be solved by adding a type signature. However,

  1. I could not figure out the full type signature for parser , and I definitely could not figure out that digit::<&str>() could solve it.

  2. Did you use some tooling to get the type signature on parser, or did you calculate it by hand?

PS: I'm using IntelliJ, which is great for situations where the IDE cna infer the type.

However, IDE can only infer a subset of what rustc infers, so when rustc fails to infer, IDE not very useful.

Well, it was most likely not done by hand, as this is just an error surrounding a generic issue and type inferences. Therefore, there was no need to derive the entire type, just the fact that there is a missing generic type, which is clearly related to the parameter of the parse method.


Actually I misread the question and a post above; but my point still holds; there was a missing signature somewhere

1 Like

rustc only told us:

Yet @jonh posted

_parser: combine::parser::range::Recognize<(combine::parser::repeat::SkipMany1<combine::parser::char::Digit<&str>>, combine::parser::choice::Optional<(combine::parser::item::Token<&str>, combine::parser::repeat::SkipMany<combine::parser::char::Digit<&str>>)>)>

If it's not done by hand, what tool was used to get the type sig?

1 Like

Well, then again there was the chance that a google search might have led to to the combine crate.

1 Like

I'm not saying this is how @jonh did it, but the following seems to work:

fn main() {
    let mut parser = recognize((
        skip_many1(digit()),
        optional((item('.'), skip_many(digit())))))
        .and_then( str_to_bignum);
    let result = parser.parse("123");
    return parser
    // println!("result: {:?}", result);
}

Note this is the version that calls parse. The code fails to compile with error:

`combine::combinator::AndThen<combine::range::Recognize<(combine::combinator::SkipMany1<combine::char::Digit<&str>>, combine::combinator::Optional<(combine::combinator::Token<&str>, combine::combinator::SkipMany<combine::char::Digit<&str>>)>)>, for<'r> fn(&'r str) -> std::result::Result<mma::data::Expr, combine::error::StringStreamError> {mma::parser::str_to_bignum}>`

Which is close to what we want.

The ugly long type was copied from vscode variable popup. (deliberate errors are another good way, as you demonstrate.)

I don't know the library but the docs are there on docs.rs so can read the functions.
The parse call hints the type is &str
Looking at the other functions in the docs gives insight into there types.
Could also use turbofish on skip_many1 or recognize, but generally easier to place the type on inside function.

I deduced enough for important trait. Filling in let _: &dyn Parser was also just reading the error and filling in the missing but could have read from docs too.

1 Like