[Solved] Rust combine type signature

#1
  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?
0 Likes

#2

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

#3

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

#4

@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?

0 Likes

#5

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.

0 Likes

#6

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

#7

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

#8

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

1 Like

#9

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.

0 Likes

#10

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