Skip to main content

rune/ast/
stmt.rs

1use core::mem::take;
2
3use crate::ast::prelude::*;
4
5#[test]
6#[cfg(not(miri))]
7fn ast_parse() {
8    rt::<ast::Stmt>("let x = 1;");
9    rt::<ast::Stmt>("#[attr] let a = f();");
10    rt::<ast::Stmt>("line!().bar()");
11
12    // A block-like expression in statement position followed by parenthesis is
13    // treated as two statements rather than a call.
14    let block = rt::<ast::Block>("{ if true { } (1, 2, 3); }");
15    assert_eq!(block.statements.len(), 2);
16    assert!(matches!(
17        block.statements[0],
18        ast::Stmt::Expr(ast::Expr::If(..))
19    ));
20
21    let block = rt::<ast::Block>("{ match x { _ => () } (1, 2, 3); }");
22    assert_eq!(block.statements.len(), 2);
23    assert!(matches!(
24        block.statements[0],
25        ast::Stmt::Expr(ast::Expr::Match(..))
26    ));
27
28    let block = rt::<ast::Block>("{ { } (1, 2, 3); }");
29    assert_eq!(block.statements.len(), 2);
30    assert!(matches!(
31        block.statements[0],
32        ast::Stmt::Expr(ast::Expr::Block(..))
33    ));
34
35    let block = rt::<ast::Block>("{ if true { } [1, 2, 3]; }");
36    assert_eq!(block.statements.len(), 2);
37
38    // In expression position the same construct is a call.
39    let expr = rt::<ast::Expr>("if true { a } else { b }(1, 2, 3)");
40    assert!(matches!(expr, ast::Expr::Call(..)));
41}
42
43/// A statement within a block.
44#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, Spanned)]
45#[non_exhaustive]
46#[allow(clippy::large_enum_variant)]
47pub enum Stmt {
48    /// A local declaration.
49    Local(Box<ast::Local>),
50    /// A declaration.
51    Item(ast::Item, #[rune(iter)] Option<T![;]>),
52    /// An expression.
53    Expr(ast::Expr),
54    /// An with a trailing semi-colon.
55    ///
56    /// And absent semicolon indicates that it is synthetic.
57    Semi(StmtSemi),
58}
59
60impl Peek for Stmt {
61    fn peek(p: &mut Peeker<'_>) -> bool {
62        matches!(p.nth(0), K![let]) || ItemOrExpr::peek(p)
63    }
64}
65
66impl Parse for Stmt {
67    fn parse(p: &mut Parser<'_>) -> Result<Self> {
68        let mut attributes = p.parse()?;
69        let visibility = p.parse()?;
70
71        if ast::Item::peek_as_item(p.peeker()) {
72            let path = p.parse::<Option<ast::Path>>()?;
73            let item: ast::Item = ast::Item::parse_with_meta_path(p, attributes, visibility, path)?;
74
75            let semi = if item.needs_semi_colon() {
76                Some(p.parse()?)
77            } else {
78                p.parse()?
79            };
80
81            return Ok(Self::Item(item, semi));
82        }
83
84        if let Some(span) = visibility.option_span() {
85            return Err(compile::Error::unsupported(span, "visibility modifier"));
86        }
87
88        let stmt = if let K![let] = p.nth(0)? {
89            let local = Box::try_new(ast::Local::parse_with_meta(p, take(&mut attributes))?)?;
90            Self::Local(local)
91        } else {
92            let expr = ast::Expr::parse_with_meta(p, &mut attributes, ast::expr::NOT_CALLABLE)?;
93
94            // Parsed an expression which can be treated directly as an item.
95            match p.parse()? {
96                Some(semi) => Self::Semi(StmtSemi::new(expr, semi)),
97                None => Self::Expr(expr),
98            }
99        };
100
101        if let Some(span) = attributes.option_span() {
102            return Err(compile::Error::unsupported(span, "attributes"));
103        }
104
105        Ok(stmt)
106    }
107}
108
109/// Parsing an item or an expression.
110#[derive(Debug, TryClone, PartialEq, Eq)]
111#[non_exhaustive]
112#[allow(clippy::large_enum_variant)]
113pub enum ItemOrExpr {
114    /// An item.
115    Item(ast::Item),
116    /// An expression.
117    Expr(ast::Expr),
118}
119
120impl Peek for ItemOrExpr {
121    fn peek(p: &mut Peeker<'_>) -> bool {
122        match p.nth(0) {
123            K![use] => true,
124            K![enum] => true,
125            K![struct] => true,
126            K![impl] => true,
127            K![async] => matches!(p.nth(1), K![fn]),
128            K![fn] => true,
129            K![mod] => true,
130            K![const] => true,
131            K![ident(..)] => true,
132            K![::] => true,
133            _ => ast::Expr::peek(p),
134        }
135    }
136}
137
138impl Parse for ItemOrExpr {
139    fn parse(p: &mut Parser<'_>) -> Result<Self> {
140        let mut attributes = p.parse()?;
141        let visibility = p.parse()?;
142
143        if ast::Item::peek_as_item(p.peeker()) {
144            let path = p.parse()?;
145            let item: ast::Item = ast::Item::parse_with_meta_path(p, attributes, visibility, path)?;
146            return Ok(Self::Item(item));
147        }
148
149        if let Some(span) = visibility.option_span() {
150            return Err(compile::Error::unsupported(span, "visibility modifier"));
151        }
152
153        let expr = ast::Expr::parse_with_meta(p, &mut attributes, ast::expr::NOT_CALLABLE)?;
154
155        if let Some(span) = attributes.option_span() {
156            return Err(compile::Error::unsupported(span, "attributes"));
157        }
158
159        Ok(Self::Expr(expr))
160    }
161}
162
163/// Key used to stort a statement into its processing order.
164#[derive(Debug, TryClone, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
165#[try_clone(copy)]
166#[non_exhaustive]
167pub enum StmtSortKey {
168    /// USe statements, that should be processed first.
169    Use,
170    /// Items.
171    Item,
172    /// Other things, that should be processed last.
173    Other,
174}
175
176/// A semi-terminated expression.
177///
178/// These have special meaning since they indicate that whatever block or
179/// function they belong to should not evaluate to the value of the expression
180/// if it is the last expression in the block.
181#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, Spanned)]
182#[non_exhaustive]
183pub struct StmtSemi {
184    /// The expression that is considered to be semi-terminated.
185    pub expr: ast::Expr,
186    /// The semi-token associated with the expression.
187    pub semi_token: T![;],
188}
189
190impl StmtSemi {
191    /// Construct a new [StmtSemi] which doesn't override
192    /// [needs_semi][StmtSemi::needs_semi].
193    pub(crate) fn new(expr: ast::Expr, semi_token: T![;]) -> Self {
194        Self { expr, semi_token }
195    }
196
197    /// Test if the statement requires a semi-colon or not.
198    pub(crate) fn needs_semi(&self) -> bool {
199        self.expr.needs_semi()
200    }
201}