solar_parse/parser/
yul.rs

1use super::SeqSep;
2use crate::{PResult, Parser};
3use smallvec::SmallVec;
4use solar_ast::{
5    AstPath, Box, DocComments, Lit, LitKind, PathSlice, StrKind, StrLit, Symbol, token::*, yul::*,
6};
7use solar_interface::{Ident, error_code, kw, sym};
8
9impl<'sess, 'ast> Parser<'sess, 'ast> {
10    /// Parses a Yul object or plain block.
11    ///
12    /// The plain block gets returned as a Yul object named "object", with a single `code` block.
13    /// See: <https://github.com/argotorg/solidity/blob/eff410eb746f202fe756a2473fd0c8a718348457/libyul/ObjectParser.cpp#L50>
14    #[instrument(level = "debug", skip_all)]
15    pub fn parse_yul_file_object(&mut self) -> PResult<'sess, Object<'ast>> {
16        let docs = self.parse_doc_comments();
17        let object = if self.check_keyword(sym::object) {
18            self.parse_yul_object(docs)
19        } else {
20            let lo = self.token.span;
21            self.parse_yul_block().map(|code| {
22                let span = lo.to(self.prev_token.span);
23                let name = StrLit { span, value: sym::object };
24                let code = CodeBlock { span, code };
25                Object { docs, span, name, code, children: Box::default(), data: Box::default() }
26            })
27        }?;
28        self.expect(TokenKind::Eof)?;
29        Ok(object)
30    }
31
32    /// Parses a Yul object.
33    ///
34    /// Reference: <https://docs.soliditylang.org/en/latest/yul.html#specification-of-yul-object>
35    pub fn parse_yul_object(&mut self, docs: DocComments<'ast>) -> PResult<'sess, Object<'ast>> {
36        let lo = self.token.span;
37        self.expect_keyword(sym::object)?;
38        let name = self.parse_str_lit()?;
39
40        self.expect(TokenKind::OpenDelim(Delimiter::Brace))?;
41        let code = self.parse_yul_code()?;
42        let mut children = Vec::new();
43        let mut data = Vec::new();
44        loop {
45            let docs = self.parse_doc_comments();
46            if self.check_keyword(sym::object) {
47                children.push(self.parse_yul_object(docs)?);
48            } else if self.check_keyword(sym::data) {
49                data.push(self.parse_yul_data()?);
50            } else {
51                break;
52            }
53        }
54        self.expect(TokenKind::CloseDelim(Delimiter::Brace))?;
55
56        let span = lo.to(self.prev_token.span);
57        let children = self.alloc_vec(children);
58        let data = self.alloc_vec(data);
59        Ok(Object { docs, span, name, code, children, data })
60    }
61
62    /// Parses a Yul code block.
63    fn parse_yul_code(&mut self) -> PResult<'sess, CodeBlock<'ast>> {
64        let lo = self.token.span;
65        self.expect_keyword(sym::code)?;
66        let code = self.parse_yul_block()?;
67        let span = lo.to(self.prev_token.span);
68        Ok(CodeBlock { span, code })
69    }
70
71    /// Parses a Yul data segment.
72    fn parse_yul_data(&mut self) -> PResult<'sess, Data<'ast>> {
73        let lo = self.token.span;
74        self.expect_keyword(sym::data)?;
75        let name = self.parse_str_lit()?;
76        let data = self.parse_yul_lit()?;
77        if !matches!(data.kind, LitKind::Str(StrKind::Str | StrKind::Hex, ..)) {
78            let msg = "only string and hex string literals are allowed in `data` segments";
79            return Err(self.dcx().err(msg).span(data.span));
80        }
81        let span = lo.to(self.prev_token.span);
82        Ok(Data { span, name, data })
83    }
84
85    fn parse_yul_lit(&mut self) -> PResult<'sess, Lit<'ast>> {
86        let (lit, subdenomination) = self.parse_lit(false)?;
87        assert!(subdenomination.is_none());
88        Ok(lit)
89    }
90
91    /// Parses a Yul statement.
92    pub fn parse_yul_stmt(&mut self) -> PResult<'sess, Stmt<'ast>> {
93        self.in_yul(Self::parse_yul_stmt_unchecked)
94    }
95
96    /// Parses a Yul statement, without setting `in_yul`.
97    pub fn parse_yul_stmt_unchecked(&mut self) -> PResult<'sess, Stmt<'ast>> {
98        self.with_recursion_limit("Yul statement", |this| {
99            let docs = this.parse_doc_comments();
100            this.parse_spanned(Self::parse_yul_stmt_kind).map(|(span, kind)| Stmt {
101                docs,
102                span,
103                kind,
104            })
105        })
106    }
107
108    /// Parses a Yul block.
109    pub fn parse_yul_block(&mut self) -> PResult<'sess, Block<'ast>> {
110        self.in_yul(Self::parse_yul_block_unchecked)
111    }
112
113    /// Parses a Yul block, without setting `in_yul`.
114    pub fn parse_yul_block_unchecked(&mut self) -> PResult<'sess, Block<'ast>> {
115        let lo = self.token.span;
116        self.parse_delim_seq(Delimiter::Brace, SeqSep::none(), true, Self::parse_yul_stmt_unchecked)
117            .map(|stmts| {
118                let span = lo.to(self.prev_token.span);
119                Block { span, stmts }
120            })
121    }
122
123    /// Parses a Yul statement kind.
124    fn parse_yul_stmt_kind(&mut self) -> PResult<'sess, StmtKind<'ast>> {
125        if self.eat_keyword(kw::Let) {
126            self.parse_yul_stmt_var_decl()
127        } else if self.eat_keyword(kw::Function) {
128            self.parse_yul_function()
129        } else if self.check(TokenKind::OpenDelim(Delimiter::Brace)) {
130            self.parse_yul_block_unchecked().map(StmtKind::Block)
131        } else if self.eat_keyword(kw::If) {
132            self.parse_yul_stmt_if()
133        } else if self.eat_keyword(kw::Switch) {
134            self.parse_yul_stmt_switch().map(StmtKind::Switch)
135        } else if self.eat_keyword(kw::For) {
136            self.parse_yul_stmt_for()
137        } else if self.eat_keyword(kw::Break) {
138            Ok(StmtKind::Break)
139        } else if self.eat_keyword(kw::Continue) {
140            Ok(StmtKind::Continue)
141        } else if self.eat_keyword(kw::Leave) {
142            Ok(StmtKind::Leave)
143        } else if self.check_ident() {
144            let path = self.parse_path_any()?;
145            if self.check(TokenKind::OpenDelim(Delimiter::Parenthesis)) {
146                let name = self.expect_single_ident_path(path);
147                self.parse_yul_expr_call_with(name).map(StmtKind::Expr)
148            } else if self.eat(TokenKind::Walrus) {
149                self.check_valid_path(path);
150                let expr = self.parse_yul_expr()?;
151                Ok(StmtKind::AssignSingle(path, expr))
152            } else if self.check(TokenKind::Comma) {
153                self.check_valid_path(path);
154                let mut paths = SmallVec::<[_; 4]>::new();
155                paths.push(path);
156                while self.eat(TokenKind::Comma) {
157                    paths.push(self.parse_yul_path()?);
158                }
159                let paths = self.alloc_smallvec(paths);
160                self.expect(TokenKind::Walrus)?;
161                let expr = self.parse_yul_expr()?;
162                let ExprKind::Call(expr) = expr.kind else {
163                    let msg = "only function calls are allowed in multi-assignment";
164                    return Err(self.dcx().err(msg).span(expr.span));
165                };
166                Ok(StmtKind::AssignMulti(paths, expr))
167            } else {
168                self.unexpected()
169            }
170        } else {
171            self.unexpected()
172        }
173    }
174
175    /// Parses a Yul variable declaration.
176    fn parse_yul_stmt_var_decl(&mut self) -> PResult<'sess, StmtKind<'ast>> {
177        let mut idents = SmallVec::<[_; 8]>::new();
178        loop {
179            idents.push(self.parse_ident()?);
180            if !self.eat(TokenKind::Comma) {
181                break;
182            }
183        }
184        let idents = self.alloc_smallvec(idents);
185        let expr = if self.eat(TokenKind::Walrus) { Some(self.parse_yul_expr()?) } else { None };
186        Ok(StmtKind::VarDecl(idents, expr))
187    }
188
189    /// Parses a Yul function definition.
190    fn parse_yul_function(&mut self) -> PResult<'sess, StmtKind<'ast>> {
191        let name = self.parse_ident()?;
192        let parameters = self.parse_paren_comma_seq(true, Self::parse_ident)?;
193        let returns = if self.eat(TokenKind::Arrow) {
194            self.parse_nodelim_comma_seq(
195                TokenKind::OpenDelim(Delimiter::Brace),
196                false,
197                Self::parse_ident,
198            )?
199        } else {
200            Default::default()
201        };
202        let body = self.parse_yul_block_unchecked()?;
203        Ok(StmtKind::FunctionDef(Function { name, parameters, returns, body }))
204    }
205
206    /// Parses a Yul if statement.
207    fn parse_yul_stmt_if(&mut self) -> PResult<'sess, StmtKind<'ast>> {
208        let cond = self.parse_yul_expr()?;
209        let body = self.parse_yul_block_unchecked()?;
210        Ok(StmtKind::If(cond, body))
211    }
212
213    /// Parses a Yul switch statement.
214    fn parse_yul_stmt_switch(&mut self) -> PResult<'sess, StmtSwitch<'ast>> {
215        let lo = self.prev_token.span;
216        let selector = self.parse_yul_expr()?;
217        let mut cases = Vec::new();
218        while self.check_keyword(kw::Case) {
219            cases.push(self.parse_yul_stmt_switch_case(kw::Case)?);
220        }
221        let default_case = if self.check_keyword(kw::Default) {
222            Some(self.parse_yul_stmt_switch_case(kw::Default)?)
223        } else {
224            None
225        };
226        if cases.is_empty() {
227            let span = lo.to(self.prev_token.span);
228            if default_case.is_none() {
229                self.dcx().err("`switch` statement has no cases").span(span).emit();
230            } else {
231                self.dcx()
232                    .warn("`switch` statement has only a default case")
233                    .span(span)
234                    .code(error_code!(9592))
235                    .emit();
236            }
237        }
238        if let Some(default_case) = default_case {
239            cases.push(default_case);
240        }
241        let cases = self.alloc_vec(cases);
242        Ok(StmtSwitch { selector, cases })
243    }
244
245    fn parse_yul_stmt_switch_case(&mut self, kw: Symbol) -> PResult<'sess, StmtSwitchCase<'ast>> {
246        self.parse_spanned(|this| {
247            debug_assert!(this.token.is_keyword(kw));
248            this.bump();
249            let constant = if kw == kw::Case {
250                let lit = this.parse_yul_lit()?;
251                this.expect_no_subdenomination();
252                Some(lit)
253            } else {
254                None
255            };
256            let body = this.parse_yul_block_unchecked()?;
257            Ok((constant, body))
258        })
259        .map(|(span, (constant, body))| StmtSwitchCase { span, constant, body })
260    }
261
262    /// Parses a Yul for statement.
263    fn parse_yul_stmt_for(&mut self) -> PResult<'sess, StmtKind<'ast>> {
264        let init = self.parse_yul_block_unchecked()?;
265        let cond = self.parse_yul_expr()?;
266        let step = self.parse_yul_block_unchecked()?;
267        let body = self.parse_yul_block_unchecked()?;
268        Ok(StmtKind::For { init, cond, step, body })
269    }
270
271    /// Parses a Yul expression.
272    fn parse_yul_expr(&mut self) -> PResult<'sess, Expr<'ast>> {
273        self.parse_spanned(Self::parse_yul_expr_kind).map(|(span, kind)| Expr { span, kind })
274    }
275
276    /// Parses a Yul expression kind.
277    fn parse_yul_expr_kind(&mut self) -> PResult<'sess, ExprKind<'ast>> {
278        if self.check_lit() {
279            // NOTE: We can't `expect_no_subdenomination` because they're valid variable names.
280            self.parse_yul_lit().map(|lit| ExprKind::Lit(self.alloc(lit)))
281        } else if self.check_path() {
282            let path = self.parse_path_any()?;
283            if self.token.is_open_delim(Delimiter::Parenthesis) {
284                // Paths are not allowed in call expressions, but Solc parses them anyway.
285                let ident = self.expect_single_ident_path(path);
286                self.parse_yul_expr_call_with(ident).map(ExprKind::Call)
287            } else {
288                self.check_valid_path(path);
289                Ok(ExprKind::Path(path))
290            }
291        } else {
292            self.unexpected()
293        }
294    }
295
296    /// Parses a Yul function call expression with the given name.
297    fn parse_yul_expr_call_with(&mut self, name: Ident) -> PResult<'sess, ExprCall<'ast>> {
298        if !name.is_yul_evm_builtin() && name.is_reserved(true) {
299            self.expected_ident_found_other(name.into(), false).unwrap_err().emit();
300        }
301        let arguments = self.parse_paren_comma_seq(true, Self::parse_yul_expr)?;
302        Ok(ExprCall { name, arguments })
303    }
304
305    /// Expects a single identifier path and returns the identifier.
306    #[track_caller]
307    fn expect_single_ident_path(&mut self, path: AstPath<'_>) -> Ident {
308        if path.segments().len() > 1 {
309            self.dcx().err("fully-qualified paths aren't allowed here").span(path.span()).emit();
310        }
311        *path.last()
312    }
313
314    fn parse_yul_path(&mut self) -> PResult<'sess, AstPath<'ast>> {
315        let path = self.parse_path_any()?;
316        self.check_valid_path(path);
317        Ok(path)
318    }
319
320    // https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.yulPath
321    #[track_caller]
322    fn check_valid_path(&mut self, path: &PathSlice) {
323        // We allow EVM builtins in any position if multiple segments are present:
324        // https://github.com/argotorg/solidity/issues/16054
325        let first = *path.first();
326        if first.is_yul_keyword() || (path.segments().len() == 1 && first.is_yul_evm_builtin()) {
327            self.expected_ident_found_other(first.into(), false).unwrap_err().emit();
328        }
329        for &ident in &path.segments()[1..] {
330            if ident.is_yul_keyword() {
331                self.expected_ident_found_other(ident.into(), false).unwrap_err().emit();
332            }
333        }
334    }
335}