syn_pub_items/
lookahead.rs

1use std::cell::RefCell;
2
3use proc_macro2::{Delimiter, Span};
4
5use buffer::Cursor;
6use error::{self, Error};
7use span::IntoSpans;
8use token::Token;
9
10/// Support for checking the next token in a stream to decide how to parse.
11///
12/// An important advantage over [`ParseStream::peek`] is that here we
13/// automatically construct an appropriate error message based on the token
14/// alternatives that get peeked. If you are producing your own error message,
15/// go ahead and use `ParseStream::peek` instead.
16///
17/// Use [`ParseStream::lookahead1`] to construct this object.
18///
19/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
20/// [`ParseStream::lookahead1`]: struct.ParseBuffer.html#method.lookahead1
21///
22/// # Example
23///
24/// ```edition2018
25/// use syn::{ConstParam, Ident, Lifetime, LifetimeDef, Result, Token, TypeParam};
26/// use syn::parse::{Parse, ParseStream};
27///
28/// // A generic parameter, a single one of the comma-separated elements inside
29/// // angle brackets in:
30/// //
31/// //     fn f<T: Clone, 'a, 'b: 'a, const N: usize>() { ... }
32/// //
33/// // On invalid input, lookahead gives us a reasonable error message.
34/// //
35/// //     error: expected one of: identifier, lifetime, `const`
36/// //       |
37/// //     5 |     fn f<!Sized>() {}
38/// //       |          ^
39/// enum GenericParam {
40///     Type(TypeParam),
41///     Lifetime(LifetimeDef),
42///     Const(ConstParam),
43/// }
44///
45/// impl Parse for GenericParam {
46///     fn parse(input: ParseStream) -> Result<Self> {
47///         let lookahead = input.lookahead1();
48///         if lookahead.peek(Ident) {
49///             input.parse().map(GenericParam::Type)
50///         } else if lookahead.peek(Lifetime) {
51///             input.parse().map(GenericParam::Lifetime)
52///         } else if lookahead.peek(Token![const]) {
53///             input.parse().map(GenericParam::Const)
54///         } else {
55///             Err(lookahead.error())
56///         }
57///     }
58/// }
59/// ```
60pub struct Lookahead1<'a> {
61    scope: Span,
62    cursor: Cursor<'a>,
63    comparisons: RefCell<Vec<&'static str>>,
64}
65
66pub fn new(scope: Span, cursor: Cursor) -> Lookahead1 {
67    Lookahead1 {
68        scope: scope,
69        cursor: cursor,
70        comparisons: RefCell::new(Vec::new()),
71    }
72}
73
74fn peek_impl(
75    lookahead: &Lookahead1,
76    peek: fn(Cursor) -> bool,
77    display: fn() -> &'static str,
78) -> bool {
79    if peek(lookahead.cursor) {
80        return true;
81    }
82    lookahead.comparisons.borrow_mut().push(display());
83    false
84}
85
86impl<'a> Lookahead1<'a> {
87    /// Looks at the next token in the parse stream to determine whether it
88    /// matches the requested type of token.
89    ///
90    /// # Syntax
91    ///
92    /// Note that this method does not use turbofish syntax. Pass the peek type
93    /// inside of parentheses.
94    ///
95    /// - `input.peek(Token![struct])`
96    /// - `input.peek(Token![==])`
97    /// - `input.peek(Ident)`
98    /// - `input.peek(Lifetime)`
99    /// - `input.peek(token::Brace)`
100    pub fn peek<T: Peek>(&self, token: T) -> bool {
101        let _ = token;
102        peek_impl(self, T::Token::peek, T::Token::display)
103    }
104
105    /// Triggers an error at the current position of the parse stream.
106    ///
107    /// The error message will identify all of the expected token types that
108    /// have been peeked against this lookahead instance.
109    pub fn error(self) -> Error {
110        let comparisons = self.comparisons.borrow();
111        match comparisons.len() {
112            0 => {
113                if self.cursor.eof() {
114                    Error::new(self.scope, "unexpected end of input")
115                } else {
116                    Error::new(self.cursor.span(), "unexpected token")
117                }
118            }
119            1 => {
120                let message = format!("expected {}", comparisons[0]);
121                error::new_at(self.scope, self.cursor, message)
122            }
123            2 => {
124                let message = format!("expected {} or {}", comparisons[0], comparisons[1]);
125                error::new_at(self.scope, self.cursor, message)
126            }
127            _ => {
128                let join = comparisons.join(", ");
129                let message = format!("expected one of: {}", join);
130                error::new_at(self.scope, self.cursor, message)
131            }
132        }
133    }
134}
135
136/// Types that can be parsed by looking at just one token.
137///
138/// Use [`ParseStream::peek`] to peek one of these types in a parse stream
139/// without consuming it from the stream.
140///
141/// This trait is sealed and cannot be implemented for types outside of Syn.
142///
143/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
144pub trait Peek: private::Sealed {
145    // Not public API.
146    #[doc(hidden)]
147    type Token: Token;
148}
149
150impl<F: FnOnce(TokenMarker) -> T, T: Token> Peek for F {
151    type Token = T;
152}
153
154pub enum TokenMarker {}
155
156impl<S> IntoSpans<S> for TokenMarker {
157    fn into_spans(self) -> S {
158        match self {}
159    }
160}
161
162pub fn is_delimiter(cursor: Cursor, delimiter: Delimiter) -> bool {
163    cursor.group(delimiter).is_some()
164}
165
166mod private {
167    use super::{Token, TokenMarker};
168    pub trait Sealed {}
169    impl<F: FnOnce(TokenMarker) -> T, T: Token> Sealed for F {}
170}