solar_parse/parser/
yul.rs

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