solar_parse/parser/
yul.rs

1use super::SeqSep;
2use crate::{PResult, Parser};
3use smallvec::SmallVec;
4use solar_ast::{token::*, yul::*, AstPath, Box, DocComments, LitKind, PathSlice, StrKind, StrLit};
5use solar_interface::{error_code, kw, sym, Ident};
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        self.parse_delim_seq(Delimiter::Brace, SeqSep::none(), true, Self::parse_yul_stmt_unchecked)
102    }
103
104    /// Parses a Yul statement kind.
105    fn parse_yul_stmt_kind(&mut self) -> PResult<'sess, StmtKind<'ast>> {
106        if self.eat_keyword(kw::Let) {
107            self.parse_yul_stmt_var_decl()
108        } else if self.eat_keyword(kw::Function) {
109            self.parse_yul_function()
110        } else if self.check(TokenKind::OpenDelim(Delimiter::Brace)) {
111            self.parse_yul_block_unchecked().map(StmtKind::Block)
112        } else if self.eat_keyword(kw::If) {
113            self.parse_yul_stmt_if()
114        } else if self.eat_keyword(kw::Switch) {
115            self.parse_yul_stmt_switch().map(StmtKind::Switch)
116        } else if self.eat_keyword(kw::For) {
117            self.parse_yul_stmt_for()
118        } else if self.eat_keyword(kw::Break) {
119            Ok(StmtKind::Break)
120        } else if self.eat_keyword(kw::Continue) {
121            Ok(StmtKind::Continue)
122        } else if self.eat_keyword(kw::Leave) {
123            Ok(StmtKind::Leave)
124        } else if self.check_ident() {
125            let path = self.parse_path_any()?;
126            if self.check(TokenKind::OpenDelim(Delimiter::Parenthesis)) {
127                let name = self.expect_single_ident_path(path);
128                self.parse_yul_expr_call_with(name).map(StmtKind::Expr)
129            } else if self.eat(TokenKind::Walrus) {
130                self.check_valid_path(path);
131                let expr = self.parse_yul_expr()?;
132                Ok(StmtKind::AssignSingle(path, expr))
133            } else if self.check(TokenKind::Comma) {
134                self.check_valid_path(path);
135                let mut paths = SmallVec::<[_; 4]>::new();
136                paths.push(path);
137                while self.eat(TokenKind::Comma) {
138                    paths.push(self.parse_path()?);
139                }
140                let paths = self.alloc_smallvec(paths);
141                self.expect(TokenKind::Walrus)?;
142                let expr = self.parse_yul_expr()?;
143                let ExprKind::Call(expr) = expr.kind else {
144                    let msg = "only function calls are allowed in multi-assignment";
145                    return Err(self.dcx().err(msg).span(expr.span));
146                };
147                Ok(StmtKind::AssignMulti(paths, expr))
148            } else {
149                self.unexpected()
150            }
151        } else {
152            self.unexpected()
153        }
154    }
155
156    /// Parses a Yul variable declaration.
157    fn parse_yul_stmt_var_decl(&mut self) -> PResult<'sess, StmtKind<'ast>> {
158        let mut idents = SmallVec::<[_; 8]>::new();
159        loop {
160            idents.push(self.parse_ident()?);
161            if !self.eat(TokenKind::Comma) {
162                break;
163            }
164        }
165        let idents = self.alloc_smallvec(idents);
166        let expr = if self.eat(TokenKind::Walrus) { Some(self.parse_yul_expr()?) } else { None };
167        Ok(StmtKind::VarDecl(idents, expr))
168    }
169
170    /// Parses a Yul function definition.
171    fn parse_yul_function(&mut self) -> PResult<'sess, StmtKind<'ast>> {
172        let name = self.parse_ident()?;
173        let parameters = self.parse_paren_comma_seq(true, Self::parse_ident)?;
174        let returns = if self.eat(TokenKind::Arrow) {
175            self.parse_nodelim_comma_seq(
176                TokenKind::OpenDelim(Delimiter::Brace),
177                false,
178                Self::parse_ident,
179            )?
180        } else {
181            Default::default()
182        };
183        let body = self.parse_yul_block_unchecked()?;
184        Ok(StmtKind::FunctionDef(Function { name, parameters, returns, body }))
185    }
186
187    /// Parses a Yul if statement.
188    fn parse_yul_stmt_if(&mut self) -> PResult<'sess, StmtKind<'ast>> {
189        let cond = self.parse_yul_expr()?;
190        let body = self.parse_yul_block_unchecked()?;
191        Ok(StmtKind::If(cond, body))
192    }
193
194    /// Parses a Yul switch statement.
195    fn parse_yul_stmt_switch(&mut self) -> PResult<'sess, StmtSwitch<'ast>> {
196        let lo = self.prev_token.span;
197        let selector = self.parse_yul_expr()?;
198        let mut branches = Vec::new();
199        while self.eat_keyword(kw::Case) {
200            let constant = self.parse_lit()?;
201            self.expect_no_subdenomination();
202            let body = self.parse_yul_block_unchecked()?;
203            branches.push(StmtSwitchCase { constant, body });
204        }
205        let branches = self.alloc_vec(branches);
206        let default_case = if self.eat_keyword(kw::Default) {
207            Some(self.parse_yul_block_unchecked()?)
208        } else {
209            None
210        };
211        if branches.is_empty() {
212            let span = lo.to(self.prev_token.span);
213            if default_case.is_none() {
214                self.dcx().err("`switch` statement has no cases").span(span).emit();
215            } else {
216                self.dcx()
217                    .warn("`switch` statement has only a default case")
218                    .span(span)
219                    .code(error_code!(9592))
220                    .emit();
221            }
222        }
223        Ok(StmtSwitch { selector, branches, default_case })
224    }
225
226    /// Parses a Yul for statement.
227    fn parse_yul_stmt_for(&mut self) -> PResult<'sess, StmtKind<'ast>> {
228        let init = self.parse_yul_block_unchecked()?;
229        let cond = self.parse_yul_expr()?;
230        let step = self.parse_yul_block_unchecked()?;
231        let body = self.parse_yul_block_unchecked()?;
232        Ok(StmtKind::For { init, cond, step, body })
233    }
234
235    /// Parses a Yul expression.
236    fn parse_yul_expr(&mut self) -> PResult<'sess, Expr<'ast>> {
237        self.parse_spanned(Self::parse_yul_expr_kind).map(|(span, kind)| Expr { span, kind })
238    }
239
240    /// Parses a Yul expression kind.
241    fn parse_yul_expr_kind(&mut self) -> PResult<'sess, ExprKind<'ast>> {
242        if self.check_lit() {
243            // NOTE: We can't `expect_no_subdenomination` because they're valid variable names.
244            self.parse_lit().map(ExprKind::Lit)
245        } else if self.check_path() {
246            let path = self.parse_path_any()?;
247            if self.token.is_open_delim(Delimiter::Parenthesis) {
248                // Paths are not allowed in call expressions, but Solc parses them anyway.
249                let ident = self.expect_single_ident_path(path);
250                self.parse_yul_expr_call_with(ident).map(ExprKind::Call)
251            } else {
252                self.check_valid_path(path);
253                Ok(ExprKind::Path(path))
254            }
255        } else {
256            self.unexpected()
257        }
258    }
259
260    /// Parses a Yul function call expression with the given name.
261    fn parse_yul_expr_call_with(&mut self, name: Ident) -> PResult<'sess, ExprCall<'ast>> {
262        if !name.is_yul_evm_builtin() && name.is_reserved(true) {
263            self.expected_ident_found_other(name.into(), false).unwrap_err().emit();
264        }
265        let arguments = self.parse_paren_comma_seq(true, Self::parse_yul_expr)?;
266        Ok(ExprCall { name, arguments })
267    }
268
269    /// Expects a single identifier path and returns the identifier.
270    #[track_caller]
271    fn expect_single_ident_path(&mut self, path: AstPath<'_>) -> Ident {
272        if path.segments().len() > 1 {
273            self.dcx().err("fully-qualified paths aren't allowed here").span(path.span()).emit();
274        }
275        *path.last()
276    }
277
278    // https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.yulPath
279    #[track_caller]
280    fn check_valid_path(&mut self, path: &PathSlice) {
281        let first = path.first();
282        if first.is_reserved(true) {
283            self.expected_ident_found_other((*first).into(), false).unwrap_err().emit();
284        }
285        for ident in &path.segments()[1..] {
286            if !ident.is_yul_evm_builtin() && ident.is_reserved(true) {
287                self.expected_ident_found_other((*ident).into(), false).unwrap_err().emit();
288            }
289        }
290    }
291}