Skip to main content

syn/
stmt.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::attr::Attribute;
4use crate::expr::Expr;
5use crate::item::Item;
6use crate::mac::Macro;
7use crate::pat::Pat;
8use crate::token;
9
10ast_struct! {
11    /// A braced block containing Rust statements.
12    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
13    pub struct Block {
14        pub brace_token: token::Brace,
15        /// Statements in a block
16        pub stmts: Vec<Stmt>,
17    }
18}
19
20ast_enum! {
21    /// A statement, usually ending in a semicolon.
22    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
23    pub enum Stmt {
24        /// A local (let) binding.
25        Local(Local),
26
27        /// An item definition.
28        Item(Item),
29
30        /// Expression, with or without trailing semicolon.
31        Expr(Expr, Option<Token![;]>),
32
33        /// A macro invocation in statement position.
34        ///
35        /// Syntactically it's ambiguous which other kind of statement this
36        /// macro would expand to. It can be any of local variable (`let`),
37        /// item, or expression.
38        Macro(StmtMacro),
39    }
40}
41
42ast_struct! {
43    /// A local `let` binding: `let x: u64 = s.parse()?;`.
44    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
45    pub struct Local {
46        pub attrs: Vec<Attribute>,
47        pub let_token: Token![let],
48        pub pat: Pat,
49        pub init: Option<LocalInit>,
50        pub semi_token: Token![;],
51    }
52}
53
54ast_struct! {
55    /// The expression assigned in a local `let` binding, including optional
56    /// diverging `else` block.
57    ///
58    /// `LocalInit` represents `= s.parse()?` in `let x: u64 = s.parse()?` and
59    /// `= r else { return }` in `let Ok(x) = r else { return }`.
60    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
61    pub struct LocalInit {
62        pub eq_token: Token![=],
63        pub expr: Box<Expr>,
64        pub diverge: Option<(Token![else], Box<Expr>)>,
65    }
66}
67
68ast_struct! {
69    /// A macro invocation in statement position.
70    ///
71    /// Syntactically it's ambiguous which other kind of statement this macro
72    /// would expand to. It can be any of local variable (`let`), item, or
73    /// expression.
74    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
75    pub struct StmtMacro {
76        pub attrs: Vec<Attribute>,
77        pub mac: Macro,
78        pub semi_token: Option<Token![;]>,
79    }
80}
81
82#[cfg(feature = "parsing")]
83pub(crate) mod parsing {
84    use crate::attr::Attribute;
85    use crate::classify;
86    use crate::error::Result;
87    use crate::expr::{Expr, ExprBlock, ExprMacro};
88    use crate::ident::Ident;
89    use crate::item;
90    use crate::mac::{self, Macro};
91    use crate::parse::discouraged::Speculative as _;
92    use crate::parse::{Parse, ParseStream};
93    use crate::pat::{Pat, PatType};
94    use crate::path::Path;
95    use crate::stmt::{Block, Local, LocalInit, Stmt, StmtMacro};
96    use crate::token;
97    use crate::ty::Type;
98    use proc_macro2::TokenStream;
99
100    struct AllowNoSemi(bool);
101
102    impl Block {
103        /// Parse the body of a block as zero or more statements, possibly
104        /// including one trailing expression.
105        ///
106        /// # Example
107        ///
108        /// ```
109        /// use syn::{braced, token, Attribute, Block, Ident, Result, Stmt, Token};
110        /// use syn::parse::{Parse, ParseStream};
111        ///
112        /// // Parse a function with no generics or parameter list.
113        /// //
114        /// //     fn playground {
115        /// //         let mut x = 1;
116        /// //         x += 1;
117        /// //         println!("{}", x);
118        /// //     }
119        /// struct MiniFunction {
120        ///     attrs: Vec<Attribute>,
121        ///     fn_token: Token![fn],
122        ///     name: Ident,
123        ///     brace_token: token::Brace,
124        ///     stmts: Vec<Stmt>,
125        /// }
126        ///
127        /// impl Parse for MiniFunction {
128        ///     fn parse(input: ParseStream) -> Result<Self> {
129        ///         let outer_attrs = input.call(Attribute::parse_outer)?;
130        ///         let fn_token: Token![fn] = input.parse()?;
131        ///         let name: Ident = input.parse()?;
132        ///
133        ///         let content;
134        ///         let brace_token = braced!(content in input);
135        ///         let inner_attrs = content.call(Attribute::parse_inner)?;
136        ///         let stmts = content.call(Block::parse_within)?;
137        ///
138        ///         Ok(MiniFunction {
139        ///             attrs: {
140        ///                 let mut attrs = outer_attrs;
141        ///                 attrs.extend(inner_attrs);
142        ///                 attrs
143        ///             },
144        ///             fn_token,
145        ///             name,
146        ///             brace_token,
147        ///             stmts,
148        ///         })
149        ///     }
150        /// }
151        /// ```
152        #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
153        pub fn parse_within(input: ParseStream) -> Result<Vec<Stmt>> {
154            let mut stmts = Vec::new();
155            loop {
156                while let semi @ Some(_) = input.parse()? {
157                    stmts.push(Stmt::Expr(Expr::Verbatim(TokenStream::new()), semi));
158                }
159                if input.is_empty() {
160                    break;
161                }
162                let stmt = parse_stmt(input, AllowNoSemi(true))?;
163                let requires_semicolon = match &stmt {
164                    Stmt::Expr(stmt, None) => classify::requires_semi_to_be_stmt(stmt),
165                    Stmt::Macro(stmt) => {
166                        stmt.semi_token.is_none() && !stmt.mac.delimiter.is_brace()
167                    }
168                    Stmt::Local(_) | Stmt::Item(_) | Stmt::Expr(_, Some(_)) => false,
169                };
170                stmts.push(stmt);
171                if input.is_empty() {
172                    break;
173                } else if requires_semicolon {
174                    return Err(input.error("unexpected token, expected `;`"));
175                }
176            }
177            Ok(stmts)
178        }
179    }
180
181    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
182    impl Parse for Block {
183        fn parse(input: ParseStream) -> Result<Self> {
184            let content;
185            Ok(Block {
186                brace_token: braced!(content in input),
187                stmts: content.call(Block::parse_within)?,
188            })
189        }
190    }
191
192    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
193    impl Parse for Stmt {
194        fn parse(input: ParseStream) -> Result<Self> {
195            let allow_nosemi = AllowNoSemi(false);
196            parse_stmt(input, allow_nosemi)
197        }
198    }
199
200    fn parse_stmt(input: ParseStream, allow_nosemi: AllowNoSemi) -> Result<Stmt> {
201        let begin = input.fork();
202        let attrs = input.call(Attribute::parse_outer)?;
203
204        // brace-style macros; paren and bracket macros get parsed as
205        // expression statements.
206        let ahead = input.fork();
207        let mut is_item_macro = false;
208        if let Ok(path) = ahead.call(Path::parse_mod_style) {
209            if ahead.peek(Token![!]) {
210                if ahead.peek2(Ident) || ahead.peek2(Token![try]) {
211                    is_item_macro = true;
212                } else if ahead.peek2(token::Brace)
213                    && !(ahead.peek3(Token![.]) && !ahead.peek3(Token![..])
214                        || ahead.peek3(Token![?]))
215                {
216                    input.advance_to(&ahead);
217                    return stmt_mac(input, attrs, path).map(Stmt::Macro);
218                }
219            }
220        }
221
222        if input.peek(Token![let]) && !input.peek(token::Group) {
223            stmt_local(input, attrs).map(Stmt::Local)
224        } else if input.peek(Token![pub])
225            || input.peek(Token![crate]) && !input.peek2(Token![::])
226            || input.peek(Token![extern])
227            || input.peek(Token![use])
228            || input.peek(Token![static])
229                && (input.peek2(Token![mut])
230                    || input.peek2(Ident)
231                        && !(input.peek2(Token![async])
232                            && (input.peek3(Token![move]) || input.peek3(Token![|]))))
233            || input.peek(Token![const])
234                && !(input.peek2(token::Brace)
235                    || input.peek2(Token![static])
236                    || input.peek2(Token![async])
237                        && !(input.peek3(Token![unsafe])
238                            || input.peek3(Token![extern])
239                            || input.peek3(Token![fn]))
240                    || input.peek2(Token![move])
241                    || input.peek2(Token![|]))
242            || input.peek(Token![unsafe]) && !input.peek2(token::Brace)
243            || input.peek(Token![async])
244                && (input.peek2(Token![unsafe])
245                    || input.peek2(Token![extern])
246                    || input.peek2(Token![fn]))
247            || input.peek(Token![fn])
248            || input.peek(Token![mod])
249            || input.peek(Token![type])
250            || input.peek(Token![struct])
251            || input.peek(Token![enum])
252            || input.peek(Token![union]) && input.peek2(Ident)
253            || input.peek(Token![auto]) && input.peek2(Token![trait])
254            || input.peek(Token![trait])
255            || input.peek(Token![default])
256                && (input.peek2(Token![unsafe]) || input.peek2(Token![impl]))
257            || input.peek(Token![impl])
258            || input.peek(Token![macro])
259            || is_item_macro
260        {
261            let item = item::parsing::parse_rest_of_item(begin, attrs, input)?;
262            Ok(Stmt::Item(item))
263        } else {
264            stmt_expr(input, allow_nosemi, attrs)
265        }
266    }
267
268    fn stmt_mac(input: ParseStream, attrs: Vec<Attribute>, path: Path) -> Result<StmtMacro> {
269        let bang_token: Token![!] = input.parse()?;
270        let (delimiter, tokens) = mac::parse_delimiter(input)?;
271        let semi_token: Option<Token![;]> = input.parse()?;
272
273        Ok(StmtMacro {
274            attrs,
275            mac: Macro {
276                path,
277                bang_token,
278                delimiter,
279                tokens,
280            },
281            semi_token,
282        })
283    }
284
285    fn stmt_local(input: ParseStream, attrs: Vec<Attribute>) -> Result<Local> {
286        let let_token: Token![let] = input.parse()?;
287
288        let mut pat = Pat::parse_single(input)?;
289        if input.peek(Token![:]) {
290            let colon_token: Token![:] = input.parse()?;
291            let ty: Type = input.parse()?;
292            pat = Pat::Type(PatType {
293                attrs: Vec::new(),
294                pat: Box::new(pat),
295                colon_token,
296                ty: Box::new(ty),
297            });
298        }
299
300        let init = if let Some(eq_token) = input.parse()? {
301            let eq_token: Token![=] = eq_token;
302            let expr: Expr = input.parse()?;
303
304            let diverge = if !classify::expr_trailing_brace(&expr) && input.peek(Token![else]) {
305                let else_token: Token![else] = input.parse()?;
306                let diverge = ExprBlock {
307                    attrs: Vec::new(),
308                    label: None,
309                    block: input.parse()?,
310                };
311                Some((else_token, Box::new(Expr::Block(diverge))))
312            } else {
313                None
314            };
315
316            Some(LocalInit {
317                eq_token,
318                expr: Box::new(expr),
319                diverge,
320            })
321        } else {
322            None
323        };
324
325        let semi_token: Token![;] = input.parse()?;
326
327        Ok(Local {
328            attrs,
329            let_token,
330            pat,
331            init,
332            semi_token,
333        })
334    }
335
336    fn stmt_expr(
337        input: ParseStream,
338        allow_nosemi: AllowNoSemi,
339        mut attrs: Vec<Attribute>,
340    ) -> Result<Stmt> {
341        let mut e = Expr::parse_with_earlier_boundary_rule(input)?;
342
343        let mut attr_target = &mut e;
344        loop {
345            attr_target = match attr_target {
346                Expr::Assign(e) => &mut e.left,
347                Expr::Binary(e) => &mut e.left,
348                Expr::Cast(e) => &mut e.expr,
349                Expr::Array(_)
350                | Expr::Async(_)
351                | Expr::Await(_)
352                | Expr::Block(_)
353                | Expr::Break(_)
354                | Expr::Call(_)
355                | Expr::Closure(_)
356                | Expr::Const(_)
357                | Expr::Continue(_)
358                | Expr::Field(_)
359                | Expr::ForLoop(_)
360                | Expr::Group(_)
361                | Expr::If(_)
362                | Expr::Index(_)
363                | Expr::Infer(_)
364                | Expr::Let(_)
365                | Expr::Lit(_)
366                | Expr::Loop(_)
367                | Expr::Macro(_)
368                | Expr::Match(_)
369                | Expr::MethodCall(_)
370                | Expr::Paren(_)
371                | Expr::Path(_)
372                | Expr::Range(_)
373                | Expr::RawAddr(_)
374                | Expr::Reference(_)
375                | Expr::Repeat(_)
376                | Expr::Return(_)
377                | Expr::Struct(_)
378                | Expr::Try(_)
379                | Expr::TryBlock(_)
380                | Expr::Tuple(_)
381                | Expr::Unary(_)
382                | Expr::Unsafe(_)
383                | Expr::While(_)
384                | Expr::Yield(_)
385                | Expr::Verbatim(_) => break,
386            };
387        }
388        attrs.extend(attr_target.replace_attrs(Vec::new()));
389        attr_target.replace_attrs(attrs);
390
391        let semi_token: Option<Token![;]> = input.parse()?;
392
393        match e {
394            Expr::Macro(ExprMacro { attrs, mac })
395                if semi_token.is_some() || mac.delimiter.is_brace() =>
396            {
397                return Ok(Stmt::Macro(StmtMacro {
398                    attrs,
399                    mac,
400                    semi_token,
401                }));
402            }
403            _ => {}
404        }
405
406        if semi_token.is_some() {
407            Ok(Stmt::Expr(e, semi_token))
408        } else if allow_nosemi.0 || !classify::requires_semi_to_be_stmt(&e) {
409            Ok(Stmt::Expr(e, None))
410        } else {
411            Err(input.error("expected semicolon"))
412        }
413    }
414}
415
416#[cfg(feature = "printing")]
417pub(crate) mod printing {
418    use crate::classify;
419    use crate::expr::{self, Expr};
420    use crate::fixup::FixupContext;
421    use crate::stmt::{Block, Local, Stmt, StmtMacro};
422    use crate::token;
423    use proc_macro2::TokenStream;
424    use quote::{ToTokens, TokenStreamExt};
425
426    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
427    impl ToTokens for Block {
428        fn to_tokens(&self, tokens: &mut TokenStream) {
429            self.brace_token.surround(tokens, |tokens| {
430                tokens.append_all(&self.stmts);
431            });
432        }
433    }
434
435    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
436    impl ToTokens for Stmt {
437        fn to_tokens(&self, tokens: &mut TokenStream) {
438            match self {
439                Stmt::Local(local) => local.to_tokens(tokens),
440                Stmt::Item(item) => item.to_tokens(tokens),
441                Stmt::Expr(expr, semi) => {
442                    expr::printing::print_expr(expr, tokens, FixupContext::new_stmt());
443                    semi.to_tokens(tokens);
444                }
445                Stmt::Macro(mac) => mac.to_tokens(tokens),
446            }
447        }
448    }
449
450    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
451    impl ToTokens for Local {
452        fn to_tokens(&self, tokens: &mut TokenStream) {
453            expr::printing::outer_attrs_to_tokens(&self.attrs, tokens);
454            self.let_token.to_tokens(tokens);
455            self.pat.to_tokens(tokens);
456            if let Some(init) = &self.init {
457                init.eq_token.to_tokens(tokens);
458                expr::printing::print_subexpression(
459                    &init.expr,
460                    init.diverge.is_some() && classify::expr_trailing_brace(&init.expr),
461                    tokens,
462                    FixupContext::NONE,
463                );
464                if let Some((else_token, diverge)) = &init.diverge {
465                    else_token.to_tokens(tokens);
466                    match &**diverge {
467                        Expr::Block(diverge) => diverge.to_tokens(tokens),
468                        _ => token::Brace::default().surround(tokens, |tokens| {
469                            expr::printing::print_expr(diverge, tokens, FixupContext::new_stmt());
470                        }),
471                    }
472                }
473            }
474            self.semi_token.to_tokens(tokens);
475        }
476    }
477
478    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
479    impl ToTokens for StmtMacro {
480        fn to_tokens(&self, tokens: &mut TokenStream) {
481            expr::printing::outer_attrs_to_tokens(&self.attrs, tokens);
482            self.mac.to_tokens(tokens);
483            self.semi_token.to_tokens(tokens);
484        }
485    }
486}