1

How are we able to pass type as an argument instead of turbofish to syn::parse::ParseBuffer::peek?

Methods are generally called like input.parse::<Token![;]>(). We pass types as generics but peek expects them as input.peek(Token![;]).

I wanted to know because if I create a type alias to Token![;] like type TokenAlias = Token![;] and use it like input.peek(TokenAlias), I get the error:

expected value, found type alias `TokenAlias` can't use a type alias as a constructor

which would have made sense if Token![;] was also not allowed.

Why do I get this error when I use type alias?

Code with TokenAlias:

// COMPILE ERROR: expected value, found type alias `TokenAlias` can't use a type alias as a constructor

type TokenAlias = Token![;];

impl Parse for PathSegments {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut segments = Punctuated::new();

        let first = parse_until(input, TokenAlias)?;
        segments.push_value(syn::parse2(first)?);

        while input.peek(TokenAlias) {
            segments.push_punct(input.parse()?);

            let next = parse_until(input, TokenAlias)?;
            segments.push_value(syn::parse2(next)?);
        }

        Ok(PathSegments { segments })
    }
}

Playground

Code with Token![;]:

// BUILDS SUCCESSFULLY

impl Parse for PathSegments {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut segments = Punctuated::new();

        let first = parse_until(input, Token![;])?;
        segments.push_value(syn::parse2(first)?);

        while input.peek(Token![;]) {
            segments.push_punct(input.parse()?);

            let next = parse_until(input, Token![;])?;
            segments.push_value(syn::parse2(next)?);
        }

        Ok(PathSegments { segments })
    }
}

Playground

Following is the source code of peek from syn:

pub fn peek<T: Peek>(&self, token: T) -> bool {
    let _ = token;
    T::Token::peek(self.cursor())
}

Source code

Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39

1 Answers1

1

The peek function accepts a token value, not a type.

The reason it accepts Token[;] is that it expands to an identifier that is the name of a type, but also of a global function hidden from docs! Your type alias refers to the type, and attempting to construct a value using constructor syntax for unit-like structs makes the compiler complain that such syntax doesn't work for type aliases. (And even if it did, Semi is not a unit-like struct, so it wouldn't work for that type in particular.)

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • That is a pretty nice way to use unit struct. Although, when I see the docs of [`syn::token::Semi`](https://docs.rs/syn/1.0.72/syn/token/struct.Semi.html), it does show a field in it named `spans`. Isn't that the type to which `Token![;]` expands? – Mihir Luthra May 16 '21 at 10:32
  • 1
    @Mihir It appears `syn` is even sneakier than I thought, and defines both a type and a function called `Semi`, and it's the function that gets picked up by `peek()`. I've now amended the answer to explain the situation. – user4815162342 May 16 '21 at 11:07
  • Yes, I was trying to check that too. When I tried assigning some wrong type to `Semi`, It says, its type is [`fn(_) -> syn::token::Semi {syn::token::Semi::<_>}`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2dc1031cb1494e05e9d5845937e9978c) – Mihir Luthra May 16 '21 at 11:09
  • 1
    @Mihir I saw that too, but it looked totally insane. Since the definition is behind a macro and the function is hidden from docs, it took a while to figure out what's going on. – user4815162342 May 16 '21 at 12:43