Parcourir la source

Improve parser (#1237)

* chore: rename test to tests
* docs: add docs and comments to doccomment
* reduce allocations in doccomment
* add doccomment tests
* docs: add comments and docs to diagnostics
* reduce allocations in parse
* use ThisError for LexicalError, change result type
* docs: document everything
* feat: add helpers mod, impl CodeLocation for PT
* fix: don't use ref
* feat: implement Display for all PT structs
* feat: implement Display for all PT enums
* feat: add Ord implementations
* feat: implement CodeLocation for smart pointers
* feat: add more helpers
* feat: finish pt::Expression impl
* fix: make helpers module public
* fix: use enum discriminant for sorting

Signed-off-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
DaniPopes il y a 2 ans
Parent
commit
5a928a3d60

+ 2 - 0
solang-parser/Cargo.toml

@@ -19,6 +19,8 @@ lalrpop-util = "0.19"
 phf = { version = "0.11", features = ["macros"] }
 unicode-xid = "0.2"
 itertools = "0.10"
+thiserror = "1.0"
+
 serde = { version = "1.0", features = ["derive"], optional = true }
 
 [dev-dependencies]

+ 50 - 4
solang-parser/src/diagnostics.rs

@@ -1,18 +1,33 @@
 // SPDX-License-Identifier: Apache-2.0
 
+//! Solidity parser diagnostics.
+
 use crate::pt;
 use crate::pt::Loc;
+use std::fmt;
 
+/// The level of a diagnostic.
 #[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
 pub enum Level {
+    /// Debug diagnostic level.
     Debug,
+    /// Info diagnostic level.
     Info,
+    /// Warning diagnostic level.
     Warning,
+    /// Error diagnostic level.
     Error,
 }
 
+impl fmt::Display for Level {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
 impl Level {
-    pub fn to_string(&self) -> &'static str {
+    /// Returns this type as a static string slice.
+    pub fn as_str(&self) -> &'static str {
         match self {
             Level::Debug => "debug",
             Level::Info => "info",
@@ -22,33 +37,51 @@ impl Level {
     }
 }
 
-#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+/// The type of a diagnostic.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum ErrorType {
+    /// No specific error type.
     None,
+    /// Parser error.
     ParserError,
+    /// Syntax error.
     SyntaxError,
+    /// Declaration error.
     DeclarationError,
+    /// Cast error.
     CastError,
+    /// Type error.
     TypeError,
+    /// Warning.
     Warning,
 }
 
-#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+/// A diagnostic note.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Note {
+    /// The code location of the note.
     pub loc: pt::Loc,
+    /// The message of the note.
     pub message: String,
 }
 
-#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
+/// A Solidity diagnostic.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Diagnostic {
+    /// The code location of the diagnostic.
     pub loc: pt::Loc,
+    /// The level of the diagnostic.
     pub level: Level,
+    /// The type of diagnostic.
     pub ty: ErrorType,
+    /// The message of the diagnostic.
     pub message: String,
+    /// Extra notes about the diagnostic.
     pub notes: Vec<Note>,
 }
 
 impl Diagnostic {
+    /// Instantiate a new Diagnostic with the given location and message at the debug level.
     pub fn debug(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Debug,
@@ -59,6 +92,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new Diagnostic with the given location and message at the info level.
     pub fn info(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Info,
@@ -69,6 +103,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new parser error Diagnostic.
     pub fn parser_error(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -79,6 +114,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new syntax error Diagnostic.
     pub fn error(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -89,6 +125,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new declaration error Diagnostic.
     pub fn decl_error(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -99,6 +136,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new cast error error Diagnostic.
     pub fn cast_error(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -109,6 +147,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new cast error error Diagnostic, with a note.
     pub fn cast_error_with_note(loc: Loc, message: String, note_loc: Loc, note: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -122,6 +161,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new type error error Diagnostic.
     pub fn type_error(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -132,6 +172,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new cast error Diagnostic at the warning level.
     pub fn cast_warning(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Warning,
@@ -142,6 +183,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new warning Diagnostic.
     pub fn warning(loc: Loc, message: String) -> Self {
         Diagnostic {
             level: Level::Warning,
@@ -152,6 +194,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new warning Diagnostic, with a note.
     pub fn warning_with_note(loc: Loc, message: String, note_loc: Loc, note: String) -> Self {
         Diagnostic {
             level: Level::Warning,
@@ -165,6 +208,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new warning Diagnostic, with multiple notes.
     pub fn warning_with_notes(loc: Loc, message: String, notes: Vec<Note>) -> Self {
         Diagnostic {
             level: Level::Warning,
@@ -175,6 +219,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new error Diagnostic, with a note.
     pub fn error_with_note(loc: Loc, message: String, note_loc: Loc, note: String) -> Self {
         Diagnostic {
             level: Level::Error,
@@ -188,6 +233,7 @@ impl Diagnostic {
         }
     }
 
+    /// Instantiate a new error Diagnostic, with multiple notes.
     pub fn error_with_notes(loc: Loc, message: String, notes: Vec<Note>) -> Self {
         Diagnostic {
             level: Level::Error,

+ 136 - 42
solang-parser/src/doccomment.rs

@@ -1,27 +1,65 @@
 // SPDX-License-Identifier: Apache-2.0
 
+//! Solidity parsed doc comments.
+//!
+//! See also the Solidity documentation on [natspec].
+//!
+//! [natspec]: https://docs.soliditylang.org/en/latest/natspec-format.html
+
 use crate::pt::Comment;
 
-#[derive(Debug, PartialEq, Eq, Clone)]
+/// A Solidity parsed doc comment.
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub enum DocComment {
-    Line { comment: DocCommentTag },
-    Block { comments: Vec<DocCommentTag> },
+    /// A line doc comment.
+    ///
+    /// `/// doc comment`
+    Line {
+        /// The single comment tag of the line.
+        comment: DocCommentTag,
+    },
+
+    /// A block doc comment.
+    ///
+    /// ```text
+    /// /**
+    ///  * block doc comment
+    ///  */
+    /// ```
+    Block {
+        /// The list of doc comment tags of the block.
+        comments: Vec<DocCommentTag>,
+    },
 }
 
 impl DocComment {
+    /// Returns the inner comments.
     pub fn comments(&self) -> Vec<&DocCommentTag> {
         match self {
             DocComment::Line { comment } => vec![comment],
             DocComment::Block { comments } => comments.iter().collect(),
         }
     }
+
+    /// Consumes `self` to return the inner comments.
+    pub fn into_comments(self) -> Vec<DocCommentTag> {
+        match self {
+            DocComment::Line { comment } => vec![comment],
+            DocComment::Block { comments } => comments,
+        }
+    }
 }
 
-#[derive(Debug, PartialEq, Eq, Clone)]
+/// A Solidity doc comment's tag, value and respective offsets.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
 pub struct DocCommentTag {
+    /// The tag of the doc comment, like the `notice` in `/// @notice Doc comment value`
     pub tag: String,
+    /// The offset of the comment's tag, relative to the start of the source string.
     pub tag_offset: usize,
+    /// The actual comment string, like `Doc comment value` in `/// @notice Doc comment value`
     pub value: String,
+    /// The offset of the comment's value, relative to the start of the source string.
     pub value_offset: usize,
 }
 
@@ -33,13 +71,10 @@ enum CommentType {
 /// From the start to end offset, filter all the doc comments out of the comments and parse
 /// them into tags with values.
 pub fn parse_doccomments(comments: &[Comment], start: usize, end: usize) -> Vec<DocComment> {
-    // first extract the tags
-    let mut tags = Vec::new();
+    let mut tags = Vec::with_capacity(comments.len());
 
-    let lines = filter_comments(comments, start, end);
-
-    for (ty, comment_lines) in lines {
-        let mut single_tags = Vec::new();
+    for (ty, comment_lines) in filter_comments(comments, start, end) {
+        let mut single_tags = Vec::with_capacity(comment_lines.len());
 
         for (start_offset, line) in comment_lines {
             let mut chars = line.char_indices().peekable();
@@ -104,7 +139,7 @@ pub fn parse_doccomments(comments: &[Comment], start: usize, end: usize) -> Vec<
 
         match ty {
             CommentType::Line if !single_tags.is_empty() => tags.push(DocComment::Line {
-                comment: single_tags[0].to_owned(),
+                comment: single_tags.swap_remove(0),
             }),
             CommentType::Block => tags.push(DocComment::Block {
                 comments: single_tags,
@@ -121,38 +156,33 @@ fn filter_comments(
     comments: &[Comment],
     start: usize,
     end: usize,
-) -> Vec<(CommentType, Vec<(usize, &str)>)> {
-    let mut res = Vec::new();
-
-    for comment in comments.iter() {
-        let mut grouped_comments = Vec::new();
-
+) -> impl Iterator<Item = (CommentType, Vec<(usize, &str)>)> {
+    comments.iter().filter_map(move |comment| {
         match comment {
-            Comment::DocLine(loc, comment) => {
-                if loc.start() >= end || loc.end() < start {
-                    continue;
-                }
-
-                // remove the leading ///
-                let leading = comment[3..]
-                    .chars()
-                    .take_while(|ch| ch.is_whitespace())
-                    .count();
-
-                grouped_comments.push((loc.start() + leading + 3, comment[3..].trim()));
+            // filter out all non-doc comments
+            Comment::Block(..) | Comment::Line(..) => None,
+            // filter out doc comments that are outside the given range
+            Comment::DocLine(loc, _) | Comment::DocBlock(loc, _)
+                if loc.start() >= end || loc.end() < start =>
+            {
+                None
+            }
 
-                res.push((CommentType::Line, grouped_comments));
+            Comment::DocLine(loc, comment) => {
+                // remove the leading /// and whitespace;
+                // if we don't find a match, default to skipping the 3 `/` bytes,
+                // since they are guaranteed to be in the comment string
+                let leading = comment
+                    .find(|c: char| c != '/' && !c.is_whitespace())
+                    .unwrap_or(3);
+                let comment = (loc.start() + leading, comment[leading..].trim_end());
+                Some((CommentType::Line, vec![comment]))
             }
             Comment::DocBlock(loc, comment) => {
-                if loc.start() >= end || loc.end() < start {
-                    continue;
-                }
-
+                // remove the leading /** and tailing */
                 let mut start = loc.start() + 3;
-
+                let mut grouped_comments = Vec::new();
                 let len = comment.len();
-
-                // remove the leading /** and tailing */
                 for s in comment[3..len - 2].lines() {
                     if let Some((i, _)) = s
                         .char_indices()
@@ -163,12 +193,76 @@ fn filter_comments(
 
                     start += s.len() + 1;
                 }
-
-                res.push((CommentType::Block, grouped_comments));
+                Some((CommentType::Block, grouped_comments))
             }
-            _ => (),
         }
-    }
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
 
-    res
+    #[test]
+    fn parse() {
+        let src = r#"
+pragma solidity ^0.8.19;
+/// @name Test
+///  no tag
+///@notice    Cool contract    
+///   @  dev     This is not a dev tag 
+/**
+ * @dev line one
+ *    line 2
+ */
+contract Test {
+    /*** my function    
+          i like whitespace    
+*/
+    function test() {}
+}
+"#;
+        let (_, comments) = crate::parse(src, 0).unwrap();
+        assert_eq!(comments.len(), 6);
+
+        let actual = parse_doccomments(&comments, 0, usize::MAX);
+        let expected = vec![
+            DocComment::Line {
+                comment: DocCommentTag {
+                    tag: "name".into(),
+                    tag_offset: 31,
+                    value: "Test\nno tag".into(),
+                    value_offset: 36,
+                },
+            },
+            DocComment::Line {
+                comment: DocCommentTag {
+                    tag: "notice".into(),
+                    tag_offset: 57,
+                    value: "Cool contract".into(),
+                    value_offset: 67,
+                },
+            },
+            DocComment::Line {
+                comment: DocCommentTag {
+                    tag: "".into(),
+                    tag_offset: 92,
+                    value: "dev     This is not a dev tag".into(),
+                    value_offset: 94,
+                },
+            },
+            // TODO: Second block is merged into the first
+            DocComment::Block {
+                comments: vec![DocCommentTag {
+                    tag: "dev".into(),
+                    tag_offset: 133,
+                    value: "line one\nline 2\nmy function\ni like whitespace".into(),
+                    value_offset: 137,
+                }],
+            },
+            DocComment::Block { comments: vec![] },
+        ];
+
+        assert_eq!(actual, expected);
+    }
 }

+ 2505 - 0
solang-parser/src/helpers/fmt.rs

@@ -0,0 +1,2505 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Implements `Display` for all parse tree data types, following the [Solidity style guide][ref].
+//!
+//! [ref]: https://docs.soliditylang.org/en/latest/style-guide.html
+
+use crate::pt;
+use std::{
+    borrow::Cow,
+    fmt::{Display, Formatter, Result, Write},
+};
+
+macro_rules! write_opt {
+    // no sep
+    ($f:expr, $opt:expr $(,)?) => {
+        if let Some(t) = $opt {
+            Display::fmt(t, $f)?;
+        }
+    };
+
+    // sep before
+    ($f:expr, $sep:literal, $opt:expr $(,)?) => {
+        if let Some(t) = $opt {
+            Display::fmt(&$sep, $f)?;
+            Display::fmt(t, $f)?;
+        }
+    };
+
+    // sep after
+    ($f:expr, $opt:expr, $sep:literal $(,)?) => {
+        if let Some(t) = $opt {
+            Display::fmt(t, $f)?;
+            Display::fmt(&$sep, $f)?;
+        }
+    };
+
+    // both
+    ($f:expr, $sep1:literal, $opt:expr, $sep2:literal $(,)?) => {
+        if let Some(t) = $opt {
+            Display::fmt(&$sep1, $f)?;
+            Display::fmt(t, $f)?;
+            Display::fmt(&$sep2, $f)?;
+        }
+    };
+}
+
+// structs
+impl Display for pt::Annotation {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_char('@')?;
+        self.id.fmt(f)?;
+        f.write_char('(')?;
+        self.value.fmt(f)?;
+        f.write_char(')')
+    }
+}
+
+impl Display for pt::Base {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.name.fmt(f)?;
+        if let Some(args) = &self.args {
+            f.write_char('(')?;
+            write_separated(args, f, ", ")?;
+            f.write_char(')')?;
+        }
+        Ok(())
+    }
+}
+
+impl Display for pt::ContractDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        f.write_char(' ')?;
+
+        write_opt!(f, &self.name, ' ');
+
+        if !self.base.is_empty() {
+            write_separated(&self.base, f, " ")?;
+            f.write_char(' ')?;
+        }
+
+        f.write_char('{')?;
+        write_separated(&self.parts, f, " ")?;
+        f.write_char('}')
+    }
+}
+
+impl Display for pt::EnumDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("enum ")?;
+        write_opt!(f, &self.name, ' ');
+
+        f.write_char('{')?;
+        write_separated_iter(self.values.iter().flatten(), f, ", ")?;
+        f.write_char('}')
+    }
+}
+
+impl Display for pt::ErrorDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.keyword.fmt(f)?;
+        write_opt!(f, ' ', &self.name);
+
+        f.write_char('(')?;
+        write_separated(&self.fields, f, ", ")?;
+        f.write_str(");")
+    }
+}
+
+impl Display for pt::ErrorParameter {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        write_opt!(f, ' ', &self.name);
+        Ok(())
+    }
+}
+
+impl Display for pt::EventDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("event")?;
+        write_opt!(f, ' ', &self.name);
+
+        f.write_char('(')?;
+        write_separated(&self.fields, f, ", ")?;
+        f.write_char(')')?;
+
+        if self.anonymous {
+            f.write_str(" anonymous")?;
+        }
+        f.write_char(';')
+    }
+}
+
+impl Display for pt::EventParameter {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        if self.indexed {
+            f.write_str(" indexed")?;
+        }
+        write_opt!(f, ' ', &self.name);
+        Ok(())
+    }
+}
+
+impl Display for pt::FunctionDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        write_opt!(f, ' ', &self.name);
+
+        f.write_char('(')?;
+        fmt_parameter_list(&self.params, f)?;
+        f.write_char(')')?;
+
+        if !self.attributes.is_empty() {
+            f.write_char(' ')?;
+            write_separated(&self.attributes, f, " ")?;
+        }
+
+        if !self.returns.is_empty() {
+            f.write_str(" returns (")?;
+            fmt_parameter_list(&self.returns, f)?;
+            f.write_char(')')?;
+        }
+
+        if let Some(body) = &self.body {
+            f.write_char(' ')?;
+            body.fmt(f)
+        } else {
+            f.write_char(';')
+        }
+    }
+}
+
+impl Display for pt::HexLiteral {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(&self.hex)
+    }
+}
+
+impl Display for pt::Identifier {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(&self.name)
+    }
+}
+
+impl Display for pt::IdentifierPath {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        write_separated(&self.identifiers, f, ".")
+    }
+}
+
+impl Display for pt::NamedArgument {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.name.fmt(f)?;
+        f.write_str(": ")?;
+        self.expr.fmt(f)
+    }
+}
+
+impl Display for pt::Parameter {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        write_opt!(f, ' ', &self.storage);
+        write_opt!(f, ' ', &self.name);
+        Ok(())
+    }
+}
+
+impl Display for pt::SourceUnit {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        write_separated(&self.0, f, "\n")
+    }
+}
+
+impl Display for pt::StringLiteral {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        if self.unicode {
+            f.write_str("unicode")?;
+        }
+        f.write_char('"')?;
+        f.write_str(&self.string)?;
+        f.write_char('"')
+    }
+}
+
+impl Display for pt::StructDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("struct ")?;
+        write_opt!(f, &self.name, ' ');
+
+        f.write_char('{')?;
+        write_separated(&self.fields, f, "; ")?;
+        if !self.fields.is_empty() {
+            f.write_char(';')?;
+        }
+        f.write_char('}')
+    }
+}
+
+impl Display for pt::TypeDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("type ")?;
+        self.name.fmt(f)?;
+        f.write_str(" is ")?;
+        self.ty.fmt(f)?;
+        f.write_char(';')
+    }
+}
+
+impl Display for pt::Using {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("using ")?;
+
+        self.list.fmt(f)?;
+
+        f.write_str(" for ")?;
+
+        match &self.ty {
+            Some(ty) => Display::fmt(ty, f),
+            None => f.write_str("*"),
+        }?;
+
+        write_opt!(f, ' ', &self.global);
+        f.write_char(';')
+    }
+}
+
+impl Display for pt::UsingFunction {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.path.fmt(f)?;
+        write_opt!(f, " as ", &self.oper);
+        Ok(())
+    }
+}
+
+impl Display for pt::VariableDeclaration {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        write_opt!(f, ' ', &self.storage);
+        write_opt!(f, ' ', &self.name);
+        Ok(())
+    }
+}
+
+impl Display for pt::VariableDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.ty.fmt(f)?;
+        write_separated(&self.attrs, f, " ")?;
+        write_opt!(f, ' ', &self.name);
+        write_opt!(f, " = ", &self.initializer);
+        f.write_char(';')
+    }
+}
+
+impl Display for pt::YulBlock {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_char('{')?;
+        write_separated(&self.statements, f, " ")?;
+        f.write_char('}')
+    }
+}
+
+impl Display for pt::YulFor {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("for ")?;
+        self.init_block.fmt(f)?;
+        f.write_char(' ')?;
+        self.condition.fmt(f)?;
+        f.write_char(' ')?;
+        self.post_block.fmt(f)?;
+        f.write_char(' ')?;
+        self.execution_block.fmt(f)
+    }
+}
+
+impl Display for pt::YulFunctionCall {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.id.fmt(f)?;
+        f.write_char('(')?;
+        write_separated(&self.arguments, f, ", ")?;
+        f.write_char(')')
+    }
+}
+
+impl Display for pt::YulFunctionDefinition {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("function ")?;
+        self.id.fmt(f)?;
+        f.write_char('(')?;
+        write_separated(&self.params, f, ", ")?;
+        f.write_str(") ")?;
+
+        if !self.returns.is_empty() {
+            f.write_str("-> (")?;
+            write_separated(&self.returns, f, ", ")?;
+            f.write_str(") ")?;
+        }
+
+        self.body.fmt(f)
+    }
+}
+
+impl Display for pt::YulSwitch {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str("switch ")?;
+        self.condition.fmt(f)?;
+        if !self.cases.is_empty() {
+            f.write_char(' ')?;
+            write_separated(&self.cases, f, " ")?;
+        }
+        write_opt!(f, " ", &self.default);
+        Ok(())
+    }
+}
+
+impl Display for pt::YulTypedIdentifier {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.id.fmt(f)?;
+        write_opt!(f, ": ", &self.ty);
+        Ok(())
+    }
+}
+
+// enums
+impl Display for pt::CatchClause {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Simple(_, param, block) => {
+                f.write_str("catch ")?;
+                write_opt!(f, '(', param, ") ");
+                block.fmt(f)
+            }
+            Self::Named(_, ident, param, block) => {
+                f.write_str("catch ")?;
+                ident.fmt(f)?;
+                f.write_char('(')?;
+                param.fmt(f)?;
+                f.write_str(") ")?;
+                block.fmt(f)
+            }
+        }
+    }
+}
+
+impl Display for pt::Comment {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.value())
+    }
+}
+
+impl Display for pt::ContractPart {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::StructDefinition(inner) => inner.fmt(f),
+            Self::EventDefinition(inner) => inner.fmt(f),
+            Self::EnumDefinition(inner) => inner.fmt(f),
+            Self::ErrorDefinition(inner) => inner.fmt(f),
+            Self::VariableDefinition(inner) => inner.fmt(f),
+            Self::FunctionDefinition(inner) => inner.fmt(f),
+            Self::TypeDefinition(inner) => inner.fmt(f),
+            Self::Annotation(inner) => inner.fmt(f),
+            Self::Using(inner) => inner.fmt(f),
+            Self::StraySemicolon(_) => f.write_char(';'),
+        }
+    }
+}
+
+impl Display for pt::ContractTy {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::ContractTy {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::Abstract(..) => "abstract contract",
+            Self::Contract(..) => "contract",
+            Self::Interface(..) => "interface",
+            Self::Library(..) => "library",
+        }
+    }
+}
+
+impl Display for pt::Expression {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::This(_) => f.write_str("this"),
+
+            Self::New(_, expr) => {
+                f.write_str("new ")?;
+                expr.fmt(f)
+            }
+            Self::Delete(_, expr) => {
+                f.write_str("delete ")?;
+                expr.fmt(f)
+            }
+
+            Self::Type(_, ty) => ty.fmt(f),
+
+            Self::Variable(ident) => ident.fmt(f),
+
+            Self::ArrayLiteral(_, exprs) => {
+                f.write_char('[')?;
+                write_separated(exprs, f, ", ")?;
+                f.write_char(']')
+            }
+            Self::ArraySubscript(_, expr1, expr2) => {
+                expr1.fmt(f)?;
+                f.write_char('[')?;
+                write_opt!(f, expr2);
+                f.write_char(']')
+            }
+            Self::ArraySlice(_, arr, l, r) => {
+                arr.fmt(f)?;
+                f.write_char('[')?;
+                write_opt!(f, l);
+                f.write_char(':')?;
+                write_opt!(f, r);
+                f.write_char(']')
+            }
+
+            Self::MemberAccess(_, expr, ident) => {
+                expr.fmt(f)?;
+                f.write_char('.')?;
+                ident.fmt(f)
+            }
+
+            Self::Parenthesis(_, expr) => {
+                f.write_char('(')?;
+                expr.fmt(f)?;
+                f.write_char(')')
+            }
+            Self::List(_, list) => {
+                f.write_char('(')?;
+                fmt_parameter_list(list, f)?;
+                f.write_char(')')
+            }
+
+            Self::AddressLiteral(_, lit) => f.write_str(lit),
+            Self::StringLiteral(vals) => write_separated(vals, f, " "),
+            Self::HexLiteral(vals) => write_separated(vals, f, " "),
+            Self::BoolLiteral(_, bool) => {
+                let s = if *bool { "true" } else { "false" };
+                f.write_str(s)
+            }
+            Self::HexNumberLiteral(_, val, unit) => {
+                // TODO: Check with and write the checksummed address when len == 42
+                // ref: https://docs.soliditylang.org/en/latest/types.html#address-literals
+                f.write_str(val)?;
+                write_opt!(f, ' ', unit);
+                Ok(())
+            }
+            Self::NumberLiteral(_, val, exp, unit) => {
+                let val = rm_underscores(val);
+                f.write_str(&val)?;
+                if !exp.is_empty() {
+                    f.write_char('e')?;
+                    let exp = rm_underscores(exp);
+                    f.write_str(&exp)?;
+                }
+                write_opt!(f, ' ', unit);
+                Ok(())
+            }
+            Self::RationalNumberLiteral(_, val, fraction, exp, unit) => {
+                let val = rm_underscores(val);
+                f.write_str(&val)?;
+
+                let mut fraction = fraction.trim_end_matches('0');
+                if fraction.is_empty() {
+                    fraction = "0"
+                }
+                f.write_char('.')?;
+                f.write_str(fraction)?;
+
+                if !exp.is_empty() {
+                    f.write_char('e')?;
+                    let exp = rm_underscores(exp);
+                    f.write_str(&exp)?;
+                }
+                write_opt!(f, ' ', unit);
+                Ok(())
+            }
+
+            Self::FunctionCall(_, expr, exprs) => {
+                expr.fmt(f)?;
+                f.write_char('(')?;
+                write_separated(exprs, f, ", ")?;
+                f.write_char(')')
+            }
+            Self::FunctionCallBlock(_, expr, block) => {
+                expr.fmt(f)?;
+                block.fmt(f)
+            }
+            Self::NamedFunctionCall(_, expr, args) => {
+                expr.fmt(f)?;
+                f.write_str("({")?;
+                write_separated(args, f, ", ")?;
+                f.write_str("})")
+            }
+
+            Self::ConditionalOperator(_, cond, l, r) => {
+                cond.fmt(f)?;
+                f.write_str(" ? ")?;
+                l.fmt(f)?;
+                f.write_str(" : ")?;
+                r.fmt(f)
+            }
+
+            Self::PreIncrement(..)
+            | Self::PostIncrement(..)
+            | Self::PreDecrement(..)
+            | Self::PostDecrement(..)
+            | Self::Not(..)
+            | Self::Complement(..)
+            | Self::UnaryPlus(..)
+            | Self::Add(..)
+            | Self::Negate(..)
+            | Self::Subtract(..)
+            | Self::Power(..)
+            | Self::Multiply(..)
+            | Self::Divide(..)
+            | Self::Modulo(..)
+            | Self::ShiftLeft(..)
+            | Self::ShiftRight(..)
+            | Self::BitwiseAnd(..)
+            | Self::BitwiseXor(..)
+            | Self::BitwiseOr(..)
+            | Self::Less(..)
+            | Self::More(..)
+            | Self::LessEqual(..)
+            | Self::MoreEqual(..)
+            | Self::And(..)
+            | Self::Or(..)
+            | Self::Equal(..)
+            | Self::NotEqual(..)
+            | Self::Assign(..)
+            | Self::AssignOr(..)
+            | Self::AssignAnd(..)
+            | Self::AssignXor(..)
+            | Self::AssignShiftLeft(..)
+            | Self::AssignShiftRight(..)
+            | Self::AssignAdd(..)
+            | Self::AssignSubtract(..)
+            | Self::AssignMultiply(..)
+            | Self::AssignDivide(..)
+            | Self::AssignModulo(..) => {
+                let (left, right) = self.components();
+                let has_spaces = self.has_space_around();
+
+                if let Some(left) = left {
+                    left.fmt(f)?;
+                    if has_spaces {
+                        f.write_char(' ')?;
+                    }
+                }
+
+                let operator = self.operator().unwrap();
+                f.write_str(operator)?;
+
+                if let Some(right) = right {
+                    if has_spaces {
+                        f.write_char(' ')?;
+                    }
+                    right.fmt(f)?;
+                }
+
+                Ok(())
+            }
+        }
+    }
+}
+impl pt::Expression {
+    /// Returns the operator string of this expression, if any.
+    #[inline]
+    pub const fn operator(&self) -> Option<&'static str> {
+        use pt::Expression::*;
+        let operator = match self {
+            New(..) => "new",
+            Delete(..) => "delete",
+
+            PreIncrement(..) | PostIncrement(..) => "++",
+            PreDecrement(..) | PostDecrement(..) => "--",
+
+            Not(..) => "!",
+            Complement(..) => "~",
+            UnaryPlus(..) | Add(..) => "+",
+            Negate(..) | Subtract(..) => "-",
+            Power(..) => "**",
+            Multiply(..) => "*",
+            Divide(..) => "/",
+            Modulo(..) => "%",
+            ShiftLeft(..) => "<<",
+            ShiftRight(..) => ">>",
+            BitwiseAnd(..) => "&",
+            BitwiseXor(..) => "^",
+            BitwiseOr(..) => "|",
+
+            Less(..) => "<",
+            More(..) => ">",
+            LessEqual(..) => "<=",
+            MoreEqual(..) => ">=",
+            And(..) => "&&",
+            Or(..) => "||",
+            Equal(..) => "==",
+            NotEqual(..) => "!=",
+
+            Assign(..) => "=",
+            AssignOr(..) => "|=",
+            AssignAnd(..) => "&=",
+            AssignXor(..) => "^=",
+            AssignShiftLeft(..) => "<<=",
+            AssignShiftRight(..) => ">>=",
+            AssignAdd(..) => "+=",
+            AssignSubtract(..) => "-=",
+            AssignMultiply(..) => "*=",
+            AssignDivide(..) => "/=",
+            AssignModulo(..) => "%=",
+
+            MemberAccess(..)
+            | ArraySubscript(..)
+            | ArraySlice(..)
+            | FunctionCall(..)
+            | FunctionCallBlock(..)
+            | NamedFunctionCall(..)
+            | ConditionalOperator(..)
+            | BoolLiteral(..)
+            | NumberLiteral(..)
+            | RationalNumberLiteral(..)
+            | HexNumberLiteral(..)
+            | StringLiteral(..)
+            | Type(..)
+            | HexLiteral(..)
+            | AddressLiteral(..)
+            | Variable(..)
+            | List(..)
+            | ArrayLiteral(..)
+            | This(..)
+            | Parenthesis(..) => return None,
+        };
+        Some(operator)
+    }
+}
+
+impl Display for pt::FunctionAttribute {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Mutability(mutability) => mutability.fmt(f),
+            Self::Visibility(visibility) => visibility.fmt(f),
+            Self::Virtual(_) => f.write_str("virtual"),
+            Self::Immutable(_) => f.write_str("immutable"),
+            Self::Override(_, idents) => {
+                f.write_str("override")?;
+                if !idents.is_empty() {
+                    f.write_char('(')?;
+                    write_separated(idents, f, ", ")?;
+                    f.write_char(')')?;
+                }
+                Ok(())
+            }
+            Self::BaseOrModifier(_, base) => base.fmt(f),
+            Self::Error(_) => Ok(()),
+        }
+    }
+}
+
+impl Display for pt::FunctionTy {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::FunctionTy {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::Constructor => "constructor",
+            Self::Function => "function",
+            Self::Fallback => "fallback",
+            Self::Receive => "receive",
+            Self::Modifier => "modifier",
+        }
+    }
+}
+
+impl Display for pt::Import {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Plain(lit, _) => {
+                f.write_str("import ")?;
+                lit.fmt(f)?;
+                f.write_char(';')
+            }
+            Self::GlobalSymbol(lit, ident, _) => {
+                f.write_str("import ")?;
+                lit.fmt(f)?;
+                f.write_str(" as ")?;
+                ident.fmt(f)?;
+                f.write_char(';')
+            }
+            Self::Rename(lit, idents, _) => {
+                f.write_str("import {")?;
+
+                // same as `write_separated_iter`
+                let mut idents = idents.iter();
+                if let Some((ident, as_ident)) = idents.next() {
+                    ident.fmt(f)?;
+                    write_opt!(f, " as ", as_ident);
+                    for (ident, as_ident) in idents {
+                        f.write_str(", ")?;
+                        ident.fmt(f)?;
+                        write_opt!(f, " as ", as_ident);
+                    }
+                }
+                f.write_str("} from ")?;
+                lit.fmt(f)?;
+                f.write_char(';')
+            }
+        }
+    }
+}
+
+impl Display for pt::Mutability {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::Mutability {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::Pure(_) => "pure",
+            Self::Constant(_) | Self::View(_) => "view",
+            Self::Payable(_) => "payable",
+        }
+    }
+}
+
+impl Display for pt::SourceUnitPart {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::ImportDirective(inner) => inner.fmt(f),
+            Self::ContractDefinition(inner) => inner.fmt(f),
+            Self::EnumDefinition(inner) => inner.fmt(f),
+            Self::StructDefinition(inner) => inner.fmt(f),
+            Self::EventDefinition(inner) => inner.fmt(f),
+            Self::ErrorDefinition(inner) => inner.fmt(f),
+            Self::FunctionDefinition(inner) => inner.fmt(f),
+            Self::VariableDefinition(inner) => inner.fmt(f),
+            Self::TypeDefinition(inner) => inner.fmt(f),
+            Self::Annotation(inner) => inner.fmt(f),
+            Self::Using(inner) => inner.fmt(f),
+            Self::PragmaDirective(_, ident, lit) => {
+                f.write_str("pragma")?;
+                write_opt!(f, ' ', ident);
+                // this isn't really a string literal, it's just parsed as one by the lexer
+                write_opt!(f, ' ', lit.as_ref().map(|lit| &lit.string));
+                f.write_char(';')
+            }
+            Self::StraySemicolon(_) => f.write_char(';'),
+        }
+    }
+}
+
+impl Display for pt::Statement {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Block {
+                unchecked,
+                statements,
+                ..
+            } => {
+                if *unchecked {
+                    f.write_str("unchecked ")?;
+                }
+
+                f.write_char('{')?;
+                write_separated(statements, f, " ")?;
+                f.write_char('}')
+            }
+            Self::Assembly {
+                dialect,
+                flags,
+                block,
+                ..
+            } => {
+                f.write_str("assembly ")?;
+                write_opt!(f, dialect, ' ');
+                if let Some(flags) = flags {
+                    if !flags.is_empty() {
+                        f.write_char('(')?;
+                        write_separated(flags, f, ", ")?;
+                        f.write_str(") ")?;
+                    }
+                }
+                block.fmt(f)
+            }
+            Self::Args(_, args) => {
+                f.write_char('{')?;
+                write_separated(args, f, ", ")?;
+                f.write_char('}')
+            }
+            Self::If(_, cond, block, end_block) => {
+                f.write_str("if (")?;
+                cond.fmt(f)?;
+                f.write_str(") ")?;
+                block.fmt(f)?;
+                write_opt!(f, " else ", end_block);
+                Ok(())
+            }
+            Self::While(_, cond, block) => {
+                f.write_str("while (")?;
+                cond.fmt(f)?;
+                f.write_str(") ")?;
+                block.fmt(f)
+            }
+            Self::Expression(_, expr) => expr.fmt(f),
+            Self::VariableDefinition(_, var, expr) => {
+                var.fmt(f)?;
+                write_opt!(f, " = ", expr);
+                f.write_char(';')
+            }
+            Self::For(_, init, cond, expr, block) => {
+                f.write_str("for (")?;
+                // edge case, don't write semicolon on a variable definition
+                match init.as_deref() {
+                    Some(var @ pt::Statement::VariableDefinition(..)) => var.fmt(f),
+                    Some(stmt) => {
+                        stmt.fmt(f)?;
+                        f.write_char(';')
+                    }
+                    None => f.write_char(';'),
+                }?;
+                write_opt!(f, ' ', cond);
+                f.write_char(';')?;
+                write_opt!(f, ' ', expr);
+                f.write_str(") ")?;
+                if let Some(block) = block {
+                    block.fmt(f)
+                } else {
+                    f.write_char(';')
+                }
+            }
+            Self::DoWhile(_, block, cond) => {
+                f.write_str("do ")?;
+                block.fmt(f)?;
+                f.write_str(" while (")?;
+                cond.fmt(f)?;
+                f.write_str(");")
+            }
+            Self::Continue(_) => f.write_str("continue;"),
+            Self::Break(_) => f.write_str("break;"),
+            Self::Return(_, expr) => {
+                f.write_str("return")?;
+                write_opt!(f, ' ', expr);
+                f.write_char(';')
+            }
+            Self::Revert(_, ident, exprs) => {
+                f.write_str("revert")?;
+                write_opt!(f, ' ', ident);
+                f.write_char('(')?;
+                write_separated(exprs, f, ", ")?;
+                f.write_str(");")
+            }
+            Self::RevertNamedArgs(_, ident, args) => {
+                f.write_str("revert")?;
+                write_opt!(f, ' ', ident);
+                f.write_char('(')?;
+                if !args.is_empty() {
+                    f.write_char('{')?;
+                    write_separated(args, f, ", ")?;
+                    f.write_char('}')?;
+                }
+                f.write_str(");")
+            }
+            Self::Emit(_, expr) => {
+                f.write_str("emit ")?;
+                expr.fmt(f)?;
+                f.write_char(';')
+            }
+            Self::Try(_, expr, returns, catch) => {
+                f.write_str("try ")?;
+                expr.fmt(f)?;
+
+                if let Some((list, stmt)) = returns {
+                    f.write_str(" returns (")?;
+                    fmt_parameter_list(list, f)?;
+                    f.write_str(") ")?;
+                    stmt.fmt(f)?;
+                }
+
+                if !catch.is_empty() {
+                    f.write_char(' ')?;
+                    write_separated(catch, f, " ")?;
+                }
+                Ok(())
+            }
+            Self::Error(_) => Ok(()),
+        }
+    }
+}
+
+impl Display for pt::StorageLocation {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::StorageLocation {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::Memory(_) => "memory",
+            Self::Storage(_) => "storage",
+            Self::Calldata(_) => "calldata",
+        }
+    }
+}
+
+impl Display for pt::Type {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Address => f.write_str("address"),
+            Self::AddressPayable => f.write_str("address payable"),
+            Self::Payable => f.write_str("payable"),
+            Self::Bool => f.write_str("bool"),
+            Self::String => f.write_str("string"),
+            Self::Rational => f.write_str("fixed"),
+            Self::DynamicBytes => f.write_str("bytes"),
+            Self::Bytes(n) => {
+                f.write_str("bytes")?;
+                n.fmt(f)
+            }
+            Self::Int(n) => {
+                f.write_str("int")?;
+                n.fmt(f)
+            }
+            Self::Uint(n) => {
+                f.write_str("uint")?;
+                n.fmt(f)
+            }
+            Self::Mapping {
+                key,
+                key_name,
+                value,
+                value_name,
+                ..
+            } => {
+                f.write_str("mapping(")?;
+
+                key.fmt(f)?;
+                write_opt!(f, ' ', key_name);
+
+                f.write_str(" => ")?;
+
+                value.fmt(f)?;
+                write_opt!(f, ' ', value_name);
+
+                f.write_char(')')
+            }
+            Self::Function {
+                params,
+                attributes,
+                returns,
+            } => {
+                f.write_str("function (")?;
+                fmt_parameter_list(params, f)?;
+                f.write_char(')')?;
+
+                if !attributes.is_empty() {
+                    f.write_char(' ')?;
+                    write_separated(attributes, f, " ")?;
+                }
+
+                if let Some((returns, attrs)) = returns {
+                    if !attrs.is_empty() {
+                        f.write_char(' ')?;
+                        write_separated(attrs, f, " ")?;
+                    }
+
+                    if !returns.is_empty() {
+                        f.write_str(" returns (")?;
+                        fmt_parameter_list(returns, f)?;
+                        f.write_char(')')?;
+                    }
+                }
+                Ok(())
+            }
+        }
+    }
+}
+
+impl Display for pt::UserDefinedOperator {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::UserDefinedOperator {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::BitwiseAnd => "&",
+            Self::Complement => "~",
+            Self::Negate => "-",
+            Self::BitwiseOr => "|",
+            Self::BitwiseXor => "^",
+            Self::Add => "+",
+            Self::Divide => "/",
+            Self::Modulo => "%",
+            Self::Multiply => "*",
+            Self::Subtract => "-",
+            Self::Equal => "==",
+            Self::More => ">",
+            Self::MoreEqual => ">=",
+            Self::Less => "<",
+            Self::LessEqual => "<=",
+            Self::NotEqual => "!=",
+        }
+    }
+}
+
+impl Display for pt::UsingList {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Library(ident) => ident.fmt(f),
+            Self::Functions(list) => {
+                f.write_char('{')?;
+                write_separated(list, f, ", ")?;
+                f.write_char('}')
+            }
+            Self::Error => Ok(()),
+        }
+    }
+}
+
+impl Display for pt::VariableAttribute {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Visibility(vis) => vis.fmt(f),
+            Self::Constant(_) => f.write_str("constant"),
+            Self::Immutable(_) => f.write_str("immutable"),
+            Self::Override(_, idents) => {
+                f.write_str("override")?;
+                if !idents.is_empty() {
+                    f.write_char('(')?;
+                    write_separated(idents, f, ", ")?;
+                    f.write_char(')')?;
+                }
+                Ok(())
+            }
+        }
+    }
+}
+
+impl Display for pt::Visibility {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        f.write_str(self.as_str())
+    }
+}
+impl pt::Visibility {
+    /// Returns the string representation of this type.
+    pub const fn as_str(&self) -> &'static str {
+        match self {
+            Self::Public(_) => "public",
+            Self::Internal(_) => "internal",
+            Self::Private(_) => "private",
+            Self::External(_) => "external",
+        }
+    }
+}
+
+impl Display for pt::YulExpression {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::BoolLiteral(_, value, ident) => {
+                let value = if *value { "true" } else { "false" };
+                f.write_str(value)?;
+                write_opt!(f, ": ", ident);
+                Ok(())
+            }
+            Self::NumberLiteral(_, value, exponent, ident) => {
+                f.write_str(value)?;
+                if !exponent.is_empty() {
+                    f.write_char('e')?;
+                    f.write_str(exponent)?;
+                }
+                write_opt!(f, ": ", ident);
+                Ok(())
+            }
+            Self::HexNumberLiteral(_, value, ident) => {
+                f.write_str(value)?;
+                write_opt!(f, ": ", ident);
+                Ok(())
+            }
+            Self::HexStringLiteral(value, ident) => {
+                value.fmt(f)?;
+                write_opt!(f, ": ", ident);
+                Ok(())
+            }
+            Self::StringLiteral(value, ident) => {
+                value.fmt(f)?;
+                write_opt!(f, ": ", ident);
+                Ok(())
+            }
+            Self::Variable(ident) => ident.fmt(f),
+            Self::FunctionCall(call) => call.fmt(f),
+            Self::SuffixAccess(_, l, r) => {
+                l.fmt(f)?;
+                f.write_char('.')?;
+                r.fmt(f)
+            }
+        }
+    }
+}
+
+impl Display for pt::YulStatement {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Block(inner) => inner.fmt(f),
+            Self::FunctionDefinition(inner) => inner.fmt(f),
+            Self::FunctionCall(inner) => inner.fmt(f),
+            Self::For(inner) => inner.fmt(f),
+            Self::Switch(inner) => inner.fmt(f),
+
+            Self::Assign(_, exprs, eq_expr) => {
+                write_separated(exprs, f, ", ")?;
+                f.write_str(" := ")?;
+                eq_expr.fmt(f)
+            }
+            Self::VariableDeclaration(_, vars, eq_expr) => {
+                f.write_str("let")?;
+                if !vars.is_empty() {
+                    f.write_char(' ')?;
+                    write_separated(vars, f, ", ")?;
+                }
+                write_opt!(f, " := ", eq_expr);
+                Ok(())
+            }
+
+            Self::If(_, expr, block) => {
+                f.write_str("if ")?;
+                expr.fmt(f)?;
+                f.write_char(' ')?;
+                block.fmt(f)
+            }
+
+            Self::Leave(_) => f.write_str("leave"),
+            Self::Break(_) => f.write_str("break"),
+            Self::Continue(_) => f.write_str("continue"),
+
+            Self::Error(_) => Ok(()),
+        }
+    }
+}
+
+impl Display for pt::YulSwitchOptions {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            Self::Case(_, expr, block) => {
+                f.write_str("case ")?;
+                expr.fmt(f)?;
+                f.write_str(" ")?;
+                block.fmt(f)
+            }
+            Self::Default(_, block) => {
+                f.write_str("default ")?;
+                block.fmt(f)
+            }
+        }
+    }
+}
+
+// These functions are private so they should be inlined by the compiler.
+// We provided these `#[inline]` hints regardless because we don't expect compile time penalties
+// or other negative impacts from them.
+// See: <https://github.com/hyperledger/solang/pull/1237#discussion_r1151557453>
+#[inline]
+fn fmt_parameter_list(list: &pt::ParameterList, f: &mut Formatter<'_>) -> Result {
+    let iter = list.iter().flat_map(|(_, param)| param);
+    write_separated_iter(iter, f, ", ")
+}
+
+#[inline]
+fn write_separated<T: Display>(slice: &[T], f: &mut Formatter<'_>, sep: &str) -> Result {
+    write_separated_iter(slice.iter(), f, sep)
+}
+
+fn write_separated_iter<T, I>(mut iter: I, f: &mut Formatter<'_>, sep: &str) -> Result
+where
+    I: Iterator<Item = T>,
+    T: Display,
+{
+    if let Some(first) = iter.next() {
+        first.fmt(f)?;
+        for item in iter {
+            f.write_str(sep)?;
+            item.fmt(f)?;
+        }
+    }
+    Ok(())
+}
+
+fn rm_underscores(s: &str) -> Cow<'_, str> {
+    if s.is_empty() {
+        Cow::Borrowed("0")
+    } else if s.contains('_') {
+        let mut s = s.to_string();
+        s.retain(|c| c != '_');
+        Cow::Owned(s)
+    } else {
+        Cow::Borrowed(s)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    macro_rules! struct_tests {
+        ($(pt::$t:ident { $( $f:ident: $e:expr ),* $(,)? } => $expected:expr),* $(,)?) => {
+            $(
+                assert_eq_display(
+                    pt::$t {
+                        loc: loc!(),
+                        $( $f: $e, )*
+                    },
+                    $expected,
+                );
+            )*
+        };
+    }
+
+    macro_rules! enum_tests {
+        ($(
+            $t:ty: {
+                $($p:expr => $expected:expr,)+
+            }
+        )+) => {
+            $(
+                $(
+                    assert_eq_display($p, $expected);
+                )+
+            )+
+        };
+    }
+
+    /// Expression
+    macro_rules! expr {
+        (this) => {
+            pt::Expression::This(loc!())
+        };
+
+        ($i:ident) => {
+            pt::Expression::Variable(id(stringify!($i)))
+        };
+
+        ($l:literal) => {
+            pt::Expression::Variable(id(stringify!($l)))
+        };
+
+        (++ $($t:tt)+) => {
+            pt::Expression::PreIncrement(loc!(), Box::new(expr!($($t)+)))
+        };
+
+        ($($t:tt)+ ++) => {
+            pt::Expression::PostIncrement(loc!(), Box::new(expr!($($t)+)))
+        };
+    }
+    macro_rules! yexpr {
+        ($i:ident) => {
+            pt::YulExpression::Variable(id(stringify!($i)))
+        };
+        ($l:literal) => {
+            pt::YulExpression::Variable(id(stringify!($l)))
+        };
+    }
+
+    /// Type
+    macro_rules! ty {
+        (uint256) => {
+            pt::Type::Uint(256)
+        };
+        (string) => {
+            pt::Type::String
+        };
+        (bytes) => {
+            pt::Type::DynamicBytes
+        };
+        (address) => {
+            pt::Type::Address
+        };
+    }
+    macro_rules! expr_ty {
+        ($($t:tt)+) => {
+            pt::Expression::Type(loc!(), ty!($($t)+))
+        };
+    }
+
+    /// Literals
+    macro_rules! lit {
+        // prefixes are not allowed in rust strings
+        (unicode $($l:literal)+) => {
+            pt::StringLiteral {
+                loc: loc!(),
+                unicode: true,
+                string: concat!( $($l),+ ).to_string(),
+            }
+        };
+
+        (hex $($l:literal)+) => {
+            pt::HexLiteral {
+                loc: loc!(),
+                hex: concat!( "hex\"", $($l),+ , "\"" ).to_string(),
+            }
+        };
+
+        ($($l:literal)+) => {
+            pt::StringLiteral {
+                loc: loc!(),
+                unicode: false,
+                string: concat!( $($l),+ ).to_string(),
+            }
+        };
+    }
+
+    /// Statement
+    macro_rules! stmt {
+        ( {} ) => {
+            pt::Statement::Block {
+                loc: loc!(),
+                unchecked: false,
+                statements: vec![],
+            }
+        };
+
+        ( unchecked { $($t:tt)* } ) => {
+            pt::Statement::Block {
+                loc: loc!(),
+                unchecked: true,
+                statements: vec![stmt!($(t)*)],
+            }
+        };
+        ( { $($t:tt)* } ) => {
+            pt::Statement::Block {
+                loc: loc!(),
+                unchecked: false,
+                statements: vec![stmt!($(t)*)],
+            }
+        };
+    }
+
+    /// IdentifierPath
+    macro_rules! idp {
+        ($($e:expr),* $(,)?) => {
+            pt::IdentifierPath {
+                loc: loc!(),
+                identifiers: vec![$(id($e)),*],
+            }
+        };
+    }
+
+    macro_rules! loc {
+        () => {
+            pt::Loc::File(0, 0, 0)
+        };
+    }
+
+    /// Param
+    macro_rules! param {
+        ($i:ident) => {
+            pt::Parameter {
+                loc: loc!(),
+                ty: expr_ty!($i),
+                storage: None,
+                name: None,
+            }
+        };
+
+        ($i:ident $n:ident) => {
+            pt::Parameter {
+                loc: loc!(),
+                ty: expr_ty!($i),
+                storage: None,
+                name: Some(id(stringify!($n))),
+            }
+        };
+
+        ($i:ident $s:ident $n:ident) => {
+            pt::Parameter {
+                loc: loc!(),
+                ty: expr_ty!($i),
+                storage: Some(storage!($s)),
+                name: Some(id(stringify!($n))),
+            }
+        };
+    }
+
+    macro_rules! storage {
+        (memory) => {
+            pt::StorageLocation::Memory(loc!())
+        };
+        (storage) => {
+            pt::StorageLocation::Storage(loc!())
+        };
+        (calldata) => {
+            pt::StorageLocation::Calldata(loc!())
+        };
+    }
+
+    /// Identifier
+    fn id(s: &str) -> pt::Identifier {
+        pt::Identifier {
+            loc: loc!(),
+            name: s.to_string(),
+        }
+    }
+
+    macro_rules! yid {
+        ($i:ident) => {
+            pt::YulTypedIdentifier {
+                loc: loc!(),
+                id: id(stringify!($i)),
+                ty: None,
+            }
+        };
+
+        ($i:ident : $t:ident) => {
+            pt::YulTypedIdentifier {
+                loc: loc!(),
+                id: id(stringify!($i)),
+                ty: Some(id(stringify!($t))),
+            }
+        };
+    }
+
+    fn var(s: &str) -> Box<pt::Expression> {
+        Box::new(pt::Expression::Variable(id(s)))
+    }
+
+    fn yul_block() -> pt::YulBlock {
+        pt::YulBlock {
+            loc: loc!(),
+            statements: vec![],
+        }
+    }
+
+    fn assert_eq_display<T: Display + std::fmt::Debug>(item: T, expected: &str) {
+        let ty = std::any::type_name::<T>();
+        let actual = item.to_string();
+        assert_eq!(actual, expected, "\"{ty}\": {item:?}");
+        // TODO: Test parsing back into an item
+        // let parsed = ;
+        // assert_eq!(parsed, item, "failed to parse display back into an item: {expected}");
+    }
+
+    #[test]
+    fn display_structs_simple() {
+        struct_tests![
+            pt::Annotation {
+                id: id("name"),
+                value: expr!(value),
+            } => "@name(value)",
+
+            pt::Base {
+                name: idp!("id", "path"),
+                args: None,
+            } => "id.path",
+            pt::Base {
+                name: idp!("id", "path"),
+                args: Some(vec![expr!(value)]),
+            } => "id.path(value)",
+            pt::Base {
+                name: idp!("id", "path"),
+                args: Some(vec![expr!(value1), expr!(value2)]),
+            } => "id.path(value1, value2)",
+
+            pt::ErrorParameter {
+                ty: expr_ty!(uint256),
+                name: None,
+            } => "uint256",
+            pt::ErrorParameter {
+                ty: expr_ty!(uint256),
+                name: Some(id("name")),
+            } => "uint256 name",
+
+            pt::EventParameter {
+                ty: expr_ty!(uint256),
+                indexed: false,
+                name: None,
+            } => "uint256",
+            pt::EventParameter {
+                ty: expr_ty!(uint256),
+                indexed: true,
+                name: None,
+            } => "uint256 indexed",
+            pt::EventParameter {
+                ty: expr_ty!(uint256),
+                indexed: false,
+                name: Some(id("name")),
+            } => "uint256 name",
+            pt::EventParameter {
+                ty: expr_ty!(uint256),
+                indexed: true,
+                name: Some(id("name")),
+            } => "uint256 indexed name",
+
+            pt::HexLiteral {
+                hex: "hex\"1234\"".into(),
+            } => "hex\"1234\"",
+            pt::HexLiteral {
+                hex: "hex\"455318975130845\"".into(),
+            } => "hex\"455318975130845\"",
+
+            pt::Identifier {
+                name: "name".to_string(),
+            } => "name",
+
+            pt::IdentifierPath {
+                identifiers: vec![id("id")],
+            } => "id",
+            pt::IdentifierPath {
+                identifiers: vec![id("id"), id("path")],
+            } => "id.path",
+            pt::IdentifierPath {
+                identifiers: vec![id("long"), id("id"), id("path")],
+            } => "long.id.path",
+
+            pt::NamedArgument {
+                name: id("name"),
+                expr: expr!(expr),
+            } => "name: expr",
+
+            pt::Parameter {
+                ty: expr_ty!(uint256),
+                storage: None,
+                name: None,
+            } => "uint256",
+            pt::Parameter {
+                ty: expr_ty!(uint256),
+                storage: None,
+                name: Some(id("name")),
+            } => "uint256 name",
+            pt::Parameter {
+                ty: expr_ty!(uint256),
+                storage: Some(pt::StorageLocation::Calldata(Default::default())),
+                name: Some(id("name")),
+            } => "uint256 calldata name",
+            pt::Parameter {
+                ty: expr_ty!(uint256),
+                storage: Some(pt::StorageLocation::Calldata(Default::default())),
+                name: None,
+            } => "uint256 calldata",
+
+            pt::StringLiteral {
+                unicode: false,
+                string: "string".into(),
+            } => "\"string\"",
+            pt::StringLiteral {
+                unicode: true,
+                string: "string".into(),
+            } => "unicode\"string\"",
+
+            pt::UsingFunction {
+                path: idp!["id", "path"],
+                oper: None,
+            } => "id.path",
+            pt::UsingFunction {
+                path: idp!["id", "path"],
+                oper: Some(pt::UserDefinedOperator::Add),
+            } => "id.path as +",
+
+            pt::VariableDeclaration {
+                ty: expr_ty!(uint256),
+                storage: None,
+                name: None,
+            } => "uint256",
+            pt::VariableDeclaration {
+                ty: expr_ty!(uint256),
+                storage: None,
+                name: Some(id("name")),
+            } => "uint256 name",
+            pt::VariableDeclaration {
+                ty: expr_ty!(uint256),
+                storage: Some(pt::StorageLocation::Calldata(Default::default())),
+                name: Some(id("name")),
+            } => "uint256 calldata name",
+            pt::VariableDeclaration {
+                ty: expr_ty!(uint256),
+                storage: Some(pt::StorageLocation::Calldata(Default::default())),
+                name: None,
+            } => "uint256 calldata",
+
+            pt::YulTypedIdentifier {
+                id: id("name"),
+                ty: None,
+            } => "name",
+            pt::YulTypedIdentifier {
+                id: id("name"),
+                ty: Some(id("uint256")),
+            } => "name: uint256",
+        ];
+    }
+
+    #[test]
+    fn display_structs_complex() {
+        struct_tests![
+            pt::ContractDefinition {
+                ty: pt::ContractTy::Contract(loc!()),
+                name: Some(id("name")),
+                base: vec![],
+                parts: vec![],
+            } => "contract name {}",
+            pt::ContractDefinition {
+                ty: pt::ContractTy::Contract(loc!()),
+                name: Some(id("name")),
+                base: vec![pt::Base {
+                    loc: loc!(),
+                    name: idp!("base"),
+                    args: None
+                }],
+                parts: vec![],
+            } => "contract name base {}",
+            pt::ContractDefinition {
+                ty: pt::ContractTy::Contract(loc!()),
+                name: Some(id("name")),
+                base: vec![pt::Base {
+                    loc: loc!(),
+                    name: idp!("base"),
+                    args: Some(vec![])
+                }],
+                parts: vec![],
+            } => "contract name base() {}",
+            pt::ContractDefinition {
+                ty: pt::ContractTy::Contract(loc!()),
+                name: Some(id("name")),
+                base: vec![pt::Base {
+                    loc: loc!(),
+                    name: idp!("base"),
+                    args: Some(vec![expr!(expr)])
+                }],
+                parts: vec![],
+            } => "contract name base(expr) {}",
+            pt::ContractDefinition {
+                ty: pt::ContractTy::Contract(loc!()),
+                name: Some(id("name")),
+                base: vec![
+                    pt::Base {
+                        loc: loc!(),
+                        name: idp!("base1"),
+                        args: None
+                    },
+                    pt::Base {
+                        loc: loc!(),
+                        name: idp!("base2"),
+                        args: None
+                    },
+                ],
+                parts: vec![],
+            } => "contract name base1 base2 {}",
+
+            pt::EnumDefinition {
+                name: Some(id("name")),
+                values: vec![]
+            } => "enum name {}",
+            pt::EnumDefinition {
+                name: Some(id("name")),
+                values: vec![Some(id("variant"))]
+            } => "enum name {variant}",
+            pt::EnumDefinition {
+                name: Some(id("name")),
+                values: vec![
+                    Some(id("variant1")),
+                    Some(id("variant2")),
+                ]
+            } => "enum name {variant1, variant2}",
+
+            pt::ErrorDefinition {
+                keyword: expr!(error),
+                name: Some(id("name")),
+                fields: vec![],
+            } => "error name();",
+            pt::ErrorDefinition {
+                keyword: expr!(error),
+                name: Some(id("name")),
+                fields: vec![pt::ErrorParameter {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    name: None,
+                }],
+            } => "error name(uint256);",
+
+            pt::EventDefinition {
+                name: Some(id("name")),
+                fields: vec![],
+                anonymous: false,
+            } => "event name();",
+            pt::EventDefinition {
+                name: Some(id("name")),
+                fields: vec![pt::EventParameter {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    indexed: false,
+                    name: None,
+                }],
+                anonymous: false,
+            } => "event name(uint256);",
+            pt::EventDefinition {
+                name: Some(id("name")),
+                fields: vec![pt::EventParameter {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    indexed: true,
+                    name: None,
+                }],
+                anonymous: false,
+            } => "event name(uint256 indexed);",
+            pt::EventDefinition {
+                name: Some(id("name")),
+                fields: vec![],
+                anonymous: true,
+            } => "event name() anonymous;",
+
+            pt::FunctionDefinition {
+                ty: pt::FunctionTy::Function,
+                name: Some(id("name")),
+                name_loc: loc!(),
+                params: vec![],
+                attributes: vec![],
+                return_not_returns: None,
+                returns: vec![],
+                body: None,
+            } => "function name();",
+            pt::FunctionDefinition {
+                ty: pt::FunctionTy::Function,
+                name: Some(id("name")),
+                name_loc: loc!(),
+                params: vec![],
+                attributes: vec![],
+                return_not_returns: None,
+                returns: vec![],
+                body: Some(stmt!({})),
+            } => "function name() {}",
+            pt::FunctionDefinition {
+                ty: pt::FunctionTy::Function,
+                name: Some(id("name")),
+                name_loc: loc!(),
+                params: vec![],
+                attributes: vec![],
+                return_not_returns: None,
+                returns: vec![(loc!(), Some(param!(uint256)))],
+                body: Some(stmt!({})),
+            } => "function name() returns (uint256) {}",
+            pt::FunctionDefinition {
+                ty: pt::FunctionTy::Function,
+                name: Some(id("name")),
+                name_loc: loc!(),
+                params: vec![],
+                attributes: vec![pt::FunctionAttribute::Virtual(loc!())],
+                return_not_returns: None,
+                returns: vec![(loc!(), Some(param!(uint256)))],
+                body: Some(stmt!({})),
+            } => "function name() virtual returns (uint256) {}",
+
+            pt::StructDefinition {
+                name: Some(id("name")),
+                fields: vec![],
+            } => "struct name {}",
+            pt::StructDefinition {
+                name: Some(id("name")),
+                fields: vec![pt::VariableDeclaration {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    storage: None,
+                    name: Some(id("a")),
+                }],
+            } => "struct name {uint256 a;}",
+            pt::StructDefinition {
+                name: Some(id("name")),
+                fields: vec![
+                    pt::VariableDeclaration {
+                        loc: loc!(),
+                        ty: expr_ty!(uint256),
+                        storage: None,
+                        name: Some(id("a")),
+                    },
+                    pt::VariableDeclaration {
+                        loc: loc!(),
+                        ty: expr_ty!(uint256),
+                        storage: None,
+                        name: Some(id("b")),
+                    }
+                ],
+            } => "struct name {uint256 a; uint256 b;}",
+
+            pt::TypeDefinition {
+                name: id("MyType"),
+                ty: expr_ty!(uint256),
+            } => "type MyType is uint256;",
+
+            pt::Using {
+                list: pt::UsingList::Library(idp!["id", "path"]),
+                ty: None,
+                global: None,
+            } => "using id.path for *;",
+            pt::Using {
+                list: pt::UsingList::Library(idp!["id", "path"]),
+                ty: Some(expr_ty!(uint256)),
+                global: None,
+            } => "using id.path for uint256;",
+            pt::Using {
+                list: pt::UsingList::Library(idp!["id", "path"]),
+                ty: Some(expr_ty!(uint256)),
+                global: Some(id("global")),
+            } => "using id.path for uint256 global;",
+            pt::Using {
+                list: pt::UsingList::Functions(vec![]),
+                ty: None,
+                global: None,
+            } => "using {} for *;",
+            pt::Using {
+                list: pt::UsingList::Functions(vec![
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!("id", "path"),
+                        oper: None,
+                    }
+                ]),
+                ty: None,
+                global: None,
+            } => "using {id.path} for *;",
+            pt::Using {
+                list: pt::UsingList::Functions(vec![
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!("id", "path"),
+                        oper: Some(pt::UserDefinedOperator::Add),
+                    }
+                ]),
+                ty: Some(expr_ty!(uint256)),
+                global: None,
+            } => "using {id.path as +} for uint256;",
+            pt::Using {
+                list: pt::UsingList::Functions(vec![
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!("id", "path1"),
+                        oper: None,
+                    },
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!("id", "path2"),
+                        oper: None,
+                    }
+                ]),
+                ty: Some(expr_ty!(uint256)),
+                global: Some(id("global")),
+            } => "using {id.path1, id.path2} for uint256 global;",
+
+            pt::YulBlock {
+                statements: vec![]
+            } => "{}",
+
+            pt::YulFor {
+                init_block: yul_block(),
+                condition: yexpr!(cond),
+                post_block: yul_block(),
+                execution_block: yul_block(),
+            } => "for {} cond {} {}",
+
+            pt::YulFunctionCall {
+                id: id("name"),
+                arguments: vec![],
+            } => "name()",
+            pt::YulFunctionCall {
+                id: id("name"),
+                arguments: vec![yexpr!(arg)],
+            } => "name(arg)",
+            pt::YulFunctionCall {
+                id: id("name"),
+                arguments: vec![yexpr!(arg1), yexpr!(arg2)],
+            } => "name(arg1, arg2)",
+
+            pt::YulFunctionDefinition {
+                id: id("name"),
+                params: vec![],
+                returns: vec![],
+                body: yul_block(),
+            } => "function name() {}",
+            pt::YulFunctionDefinition {
+                id: id("name"),
+                params: vec![yid!(param1: a), yid!(param2: b)],
+                returns: vec![],
+                body: yul_block(),
+            } => "function name(param1: a, param2: b) {}",
+            pt::YulFunctionDefinition {
+                id: id("name"),
+                params: vec![yid!(param1: a), yid!(param2: b)],
+                returns: vec![yid!(ret1: c), yid!(ret2: d)],
+                body: yul_block(),
+            } => "function name(param1: a, param2: b) -> (ret1: c, ret2: d) {}",
+
+            pt::YulSwitch {
+                condition: yexpr!(cond),
+                cases: vec![pt::YulSwitchOptions::Case(loc!(), yexpr!(expr), yul_block())],
+                default: None,
+            } => "switch cond case expr {}",
+            pt::YulSwitch {
+                condition: yexpr!(cond),
+                cases: vec![
+                    pt::YulSwitchOptions::Case(loc!(), yexpr!(0), yul_block()),
+                    pt::YulSwitchOptions::Case(loc!(), yexpr!(1), yul_block()),
+                ],
+                default: None,
+            } => "switch cond case 0 {} case 1 {}",
+            pt::YulSwitch {
+                condition: yexpr!(cond),
+                cases: vec![pt::YulSwitchOptions::Case(loc!(), yexpr!(0), yul_block())],
+                default: Some(pt::YulSwitchOptions::Default(loc!(), yul_block())),
+            } => "switch cond case 0 {} default {}",
+        ];
+    }
+
+    #[test]
+    fn display_enums() {
+        enum_tests![
+            // https://docs.soliditylang.org/en/latest/control-structures.html#try-catch
+            pt::CatchClause: {
+                pt::CatchClause::Named(loc!(), id("Error"), param!(string memory reason), stmt!({}))
+                    => "catch Error(string memory reason) {}",
+                pt::CatchClause::Named(loc!(), id("Panic"), param!(uint256 errorCode), stmt!({}))
+                    => "catch Panic(uint256 errorCode) {}",
+
+                pt::CatchClause::Simple(loc!(), None, stmt!({})) => "catch {}",
+                pt::CatchClause::Simple(loc!(), Some(param!(uint256)), stmt!({}))
+                    => "catch (uint256) {}",
+                pt::CatchClause::Simple(loc!(), Some(param!(bytes memory data)), stmt!({}))
+                    => "catch (bytes memory data) {}",
+            }
+
+            pt::Comment: {
+                pt::Comment::Line(loc!(), "// line".into()) => "// line",
+                pt::Comment::Block(loc!(), "/* \nblock\n*/".into()) => "/* \nblock\n*/",
+                pt::Comment::DocLine(loc!(), "/// doc line".into()) => "/// doc line",
+                pt::Comment::DocBlock(loc!(), "/**\n * doc block\n */".into()) => "/**\n * doc block\n */",
+            }
+
+            // tested individually
+            pt::ContractPart: {
+                pt::ContractPart::StraySemicolon(loc!()) => ";",
+            }
+
+            pt::ContractTy: {
+                pt::ContractTy::Abstract(loc!()) => "abstract contract",
+                pt::ContractTy::Contract(loc!()) => "contract",
+                pt::ContractTy::Interface(loc!()) => "interface",
+                pt::ContractTy::Library(loc!()) => "library",
+            }
+
+            pt::Expression: {
+                pt::Expression::This(loc!()) => "this",
+
+                pt::Expression::New(loc!(), Box::new(expr_ty!(uint256))) => "new uint256",
+                pt::Expression::Delete(loc!(), Box::new(expr_ty!(uint256))) => "delete uint256",
+
+                pt::Expression::Type(loc!(), ty!(uint256)) => "uint256",
+                pt::Expression::Variable(id("myVar")) => "myVar",
+
+                pt::Expression::ArrayLiteral(loc!(), vec![expr!(1), expr!(2)]) => "[1, 2]",
+
+                pt::Expression::ArraySubscript(loc!(), Box::new(expr!(arr)), None) => "arr[]",
+                pt::Expression::ArraySubscript(loc!(), Box::new(expr!(arr)), Some(Box::new(expr!(0)))) => "arr[0]",
+                pt::Expression::ArraySlice(loc!(), Box::new(expr!(arr)), None, None) => "arr[:]",
+                pt::Expression::ArraySlice(loc!(), Box::new(expr!(arr)), Some(Box::new(expr!(left))), None)
+                    => "arr[left:]",
+                pt::Expression::ArraySlice(loc!(), Box::new(expr!(arr)), None, Some(Box::new(expr!(right))))
+                    => "arr[:right]",
+                pt::Expression::ArraySlice(loc!(), Box::new(expr!(arr)), Some(Box::new(expr!(left))), Some(Box::new(expr!(right))))
+                    => "arr[left:right]",
+
+                pt::Expression::MemberAccess(loc!(), Box::new(expr!(struct)), id("access")) => "struct.access",
+
+                pt::Expression::Parenthesis(loc!(), Box::new(expr!(var))) => "(var)",
+                pt::Expression::List(loc!(), vec![]) => "()",
+                pt::Expression::List(loc!(), vec![(loc!(), Some(param!(address)))])
+                    => "(address)",
+                pt::Expression::List(loc!(), vec![(loc!(), Some(param!(address))), (loc!(), Some(param!(uint256)))])
+                    => "(address, uint256)",
+
+                pt::Expression::AddressLiteral(loc!(), "0x1234".into()) => "0x1234",
+                pt::Expression::StringLiteral(vec![lit!(unicode "¹²³")]) => "unicode\"¹²³\"",
+                pt::Expression::HexLiteral(vec![lit!(hex "00112233")]) => "hex\"00112233\"",
+                pt::Expression::BoolLiteral(loc!(), true) => "true",
+                pt::Expression::BoolLiteral(loc!(), false) => "false",
+
+                pt::Expression::HexNumberLiteral(loc!(), "0x1234".into(), None) => "0x1234",
+                pt::Expression::HexNumberLiteral(loc!(), "0x1234".into(), Some(id("gwei"))) => "0x1234 gwei",
+                pt::Expression::NumberLiteral(loc!(), "_123_4_".into(), "".into(), None)
+                    => "1234",
+                pt::Expression::NumberLiteral(loc!(), "_1_234_".into(), "_2".into(), None)
+                    => "1234e2",
+                pt::Expression::NumberLiteral(loc!(), "_1_23_4".into(), "".into(), Some(id("gwei")))
+                    => "1234 gwei",
+                pt::Expression::NumberLiteral(loc!(), "1_23_4_".into(), "2_".into(), Some(id("gwei")))
+                    => "1234e2 gwei",
+                pt::Expression::RationalNumberLiteral(loc!(), "1_23_4_".into(), "".into(), "".into(), None)
+                    => "1234.0",
+                pt::Expression::RationalNumberLiteral(loc!(), "_1_23_4".into(), "0".into(), "_2".into(), None)
+                    => "1234.0e2",
+                pt::Expression::RationalNumberLiteral(loc!(), "_1_234_".into(), "09".into(), "".into(), Some(id("gwei")))
+                    => "1234.09 gwei",
+                pt::Expression::RationalNumberLiteral(loc!(), "_123_4_".into(), "90".into(), "2_".into(), Some(id("gwei")))
+                    => "1234.9e2 gwei",
+
+                pt::Expression::FunctionCall(loc!(), Box::new(expr!(func)), vec![]) => "func()",
+                pt::Expression::FunctionCall(loc!(), Box::new(expr!(func)), vec![expr!(arg)])
+                    => "func(arg)",
+                pt::Expression::FunctionCall(loc!(), Box::new(expr!(func)), vec![expr!(arg1), expr!(arg2)])
+                    => "func(arg1, arg2)",
+                pt::Expression::FunctionCallBlock(loc!(), Box::new(expr!(func)), Box::new(stmt!({})))
+                    => "func{}",
+                pt::Expression::NamedFunctionCall(loc!(), Box::new(expr!(func)), vec![])
+                    => "func({})",
+                pt::Expression::NamedFunctionCall(loc!(), Box::new(expr!(func)), vec![pt::NamedArgument {
+                    loc: loc!(),
+                    name: id("arg"),
+                    expr: expr!(value),
+                }]) => "func({arg: value})",
+                pt::Expression::NamedFunctionCall(loc!(), Box::new(expr!(func)), vec![
+                    pt::NamedArgument {
+                        loc: loc!(),
+                        name: id("arg1"),
+                        expr: expr!(value1),
+                    },
+                    pt::NamedArgument {
+                        loc: loc!(),
+                        name: id("arg2"),
+                        expr: expr!(value2),
+                    }
+                ]) => "func({arg1: value1, arg2: value2})",
+
+                pt::Expression::PreIncrement(loc!(), var("a")) => "++a",
+                pt::Expression::PostIncrement(loc!(), var("a")) => "a++",
+                pt::Expression::PreDecrement(loc!(), var("a")) => "--a",
+                pt::Expression::PostDecrement(loc!(), var("a")) => "a--",
+                pt::Expression::Not(loc!(), var("a")) => "!a",
+                pt::Expression::Complement(loc!(), var("a")) => "~a",
+                pt::Expression::UnaryPlus(loc!(), var("a")) => "+a",
+                pt::Expression::Negate(loc!(), var("a")) => "-a",
+
+                pt::Expression::Add(loc!(), var("a"), var("b")) => "a + b",
+                pt::Expression::Subtract(loc!(), var("a"), var("b")) => "a - b",
+                pt::Expression::Power(loc!(), var("a"), var("b")) => "a ** b",
+                pt::Expression::Multiply(loc!(), var("a"), var("b")) => "a * b",
+                pt::Expression::Divide(loc!(), var("a"), var("b")) => "a / b",
+                pt::Expression::Modulo(loc!(), var("a"), var("b")) => "a % b",
+                pt::Expression::ShiftLeft(loc!(), var("a"), var("b")) => "a << b",
+                pt::Expression::ShiftRight(loc!(), var("a"), var("b")) => "a >> b",
+                pt::Expression::BitwiseAnd(loc!(), var("a"), var("b")) => "a & b",
+                pt::Expression::BitwiseXor(loc!(), var("a"), var("b")) => "a ^ b",
+                pt::Expression::BitwiseOr(loc!(), var("a"), var("b")) => "a | b",
+                pt::Expression::Less(loc!(), var("a"), var("b")) => "a < b",
+                pt::Expression::More(loc!(), var("a"), var("b")) => "a > b",
+                pt::Expression::LessEqual(loc!(), var("a"), var("b")) => "a <= b",
+                pt::Expression::MoreEqual(loc!(), var("a"), var("b")) => "a >= b",
+                pt::Expression::And(loc!(), var("a"), var("b")) => "a && b",
+                pt::Expression::Or(loc!(), var("a"), var("b")) => "a || b",
+                pt::Expression::Equal(loc!(), var("a"), var("b")) => "a == b",
+                pt::Expression::NotEqual(loc!(), var("a"), var("b")) => "a != b",
+
+                pt::Expression::Assign(loc!(), var("a"), var("b")) => "a = b",
+                pt::Expression::AssignOr(loc!(), var("a"), var("b")) => "a |= b",
+                pt::Expression::AssignAnd(loc!(), var("a"), var("b")) => "a &= b",
+                pt::Expression::AssignXor(loc!(), var("a"), var("b")) => "a ^= b",
+                pt::Expression::AssignShiftLeft(loc!(), var("a"), var("b")) => "a <<= b",
+                pt::Expression::AssignShiftRight(loc!(), var("a"), var("b")) => "a >>= b",
+                pt::Expression::AssignAdd(loc!(), var("a"), var("b")) => "a += b",
+                pt::Expression::AssignSubtract(loc!(), var("a"), var("b")) => "a -= b",
+                pt::Expression::AssignMultiply(loc!(), var("a"), var("b")) => "a *= b",
+                pt::Expression::AssignDivide(loc!(), var("a"), var("b")) => "a /= b",
+                pt::Expression::AssignModulo(loc!(), var("a"), var("b")) => "a %= b",
+            }
+
+            pt::FunctionAttribute: {
+                pt::FunctionAttribute::Virtual(loc!()) => "virtual",
+                pt::FunctionAttribute::Immutable(loc!()) => "immutable",
+
+                pt::FunctionAttribute::Override(loc!(), vec![]) => "override",
+                pt::FunctionAttribute::Override(loc!(), vec![idp!["a", "b"]]) => "override(a.b)",
+                pt::FunctionAttribute::Override(loc!(), vec![idp!["a", "b"], idp!["c", "d"]])
+                    => "override(a.b, c.d)",
+            }
+
+            pt::FunctionTy: {
+                pt::FunctionTy::Constructor => "constructor",
+                pt::FunctionTy::Function => "function",
+                pt::FunctionTy::Fallback => "fallback",
+                pt::FunctionTy::Receive => "receive",
+                pt::FunctionTy::Modifier => "modifier",
+            }
+
+            pt::Import: {
+                pt::Import::Plain(lit!("path/to/import"), loc!()) => "import \"path/to/import\";",
+
+                pt::Import::GlobalSymbol(lit!("path-to-import"), id("ImportedContract"), loc!())
+                    => "import \"path-to-import\" as ImportedContract;",
+
+                pt::Import::Rename(lit!("import\\to\\path"), vec![], loc!())
+                    => "import {} from \"import\\to\\path\";",
+                pt::Import::Rename(lit!("import\\to\\path"), vec![(id("A"), None), (id("B"), Some(id("C")))], loc!())
+                    => "import {A, B as C} from \"import\\to\\path\";",
+            }
+
+            pt::Mutability: {
+                pt::Mutability::Pure(loc!()) => "pure",
+                pt::Mutability::View(loc!()) => "view",
+                pt::Mutability::Constant(loc!()) => "view",
+                pt::Mutability::Payable(loc!()) => "payable",
+            }
+
+            pt::SourceUnitPart: {
+                // rest tested individually
+
+                pt::SourceUnitPart::PragmaDirective(loc!(), None, None) => "pragma;",
+                pt::SourceUnitPart::PragmaDirective(loc!(), Some(id("solidity")), None)
+                    => "pragma solidity;",
+                pt::SourceUnitPart::PragmaDirective(loc!(), Some(id("solidity")), Some(lit!("0.8.0")))
+                    => "pragma solidity 0.8.0;",
+
+                pt::SourceUnitPart::StraySemicolon(loc!()) => ";",
+            }
+
+            pt::Statement: {
+                pt::Statement::Assembly {
+                    loc: loc!(),
+                    dialect: None,
+                    flags: None,
+                    block: yul_block(),
+                } => "assembly {}",
+                pt::Statement::Assembly {
+                    loc: loc!(),
+                    dialect: None,
+                    flags: Some(vec![lit!("memory-safe")]),
+                    block: yul_block(),
+                } => "assembly (\"memory-safe\") {}",
+                pt::Statement::Assembly {
+                    loc: loc!(),
+                    dialect: None,
+                    flags: Some(vec![lit!("memory-safe"), lit!("second-flag")]),
+                    block: yul_block(),
+                } => "assembly (\"memory-safe\", \"second-flag\") {}",
+
+                pt::Statement::Args(loc!(), vec![]) => "{}",
+                pt::Statement::Args(loc!(), vec![
+                    pt::NamedArgument {
+                        loc: loc!(),
+                        name: id("name"),
+                        expr: expr!(value),
+                    },
+                ]) => "{name: value}",
+                pt::Statement::Args(loc!(), vec![
+                    pt::NamedArgument {
+                        loc: loc!(),
+                        name: id("name1"),
+                        expr: expr!(value1),
+                    },
+                    pt::NamedArgument {
+                        loc: loc!(),
+                        name: id("name2"),
+                        expr: expr!(value2),
+                    },
+                ]) => "{name1: value1, name2: value2}",
+
+                pt::Statement::If(loc!(), expr!(true), Box::new(stmt!({})), None) => "if (true) {}",
+                pt::Statement::If(loc!(), expr!(true), Box::new(stmt!({})), Some(Box::new(stmt!({}))))
+                    => "if (true) {} else {}",
+
+                pt::Statement::While(loc!(), expr!(true), Box::new(stmt!({}))) => "while (true) {}",
+
+                pt::Statement::Expression(loc!(), expr!(true)) => "true",
+
+                pt::Statement::VariableDefinition(loc!(), pt::VariableDeclaration {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    storage: None,
+                    name: Some(id("a")),
+                }, None) => "uint256 a;",
+                pt::Statement::VariableDefinition(loc!(), pt::VariableDeclaration {
+                    loc: loc!(),
+                    ty: expr_ty!(uint256),
+                    storage: None,
+                    name: Some(id("a")),
+                }, Some(expr!(0))) => "uint256 a = 0;",
+
+                pt::Statement::For(loc!(), None, None, None, Some(Box::new(stmt!({}))))
+                    => "for (;;) {}",
+                pt::Statement::For(loc!(), Some(Box::new(pt::Statement::VariableDefinition(
+                    loc!(),
+                    pt::VariableDeclaration {
+                        loc: loc!(),
+                        ty: expr_ty!(uint256),
+                        storage: None,
+                        name: Some(id("a")),
+                    },
+                    None
+                ))), None, None, Some(Box::new(stmt!({}))))
+                    => "for (uint256 a;;) {}",
+                pt::Statement::For(loc!(), None, Some(Box::new(expr!(true))), None, Some(Box::new(stmt!({}))))
+                    => "for (; true;) {}",
+                pt::Statement::For(
+                    loc!(),
+                    None,
+                    Some(Box::new(expr!(true))),
+                    Some(Box::new(pt::Statement::Expression(loc!(), expr!(++i)))),
+                    Some(Box::new(stmt!({})))
+                ) => "for (; true; ++i) {}",
+
+                pt::Statement::DoWhile(loc!(), Box::new(stmt!({})), expr!(true))
+                    => "do {} while (true);",
+
+                pt::Statement::Continue(loc!()) => "continue;",
+                pt::Statement::Break(loc!()) => "break;",
+
+                pt::Statement::Return(loc!(), None) => "return;",
+                pt::Statement::Return(loc!(), Some(expr!(true))) => "return true;",
+
+                pt::Statement::Revert(loc!(), None, vec![]) => "revert();",
+                pt::Statement::Revert(loc!(), None, vec![expr!("error")])
+                    => "revert(\"error\");",
+                pt::Statement::Revert(loc!(), Some(idp!("my", "error")), vec![expr!("error")])
+                    => "revert my.error(\"error\");",
+
+                pt::Statement::RevertNamedArgs(loc!(), None, vec![]) => "revert();",
+                pt::Statement::RevertNamedArgs(loc!(), None, vec![pt::NamedArgument {
+                    loc: loc!(),
+                    name: id("name"),
+                    expr: expr!(value),
+                }]) => "revert({name: value});",
+                pt::Statement::RevertNamedArgs(loc!(), Some(idp!("my", "error")), vec![pt::NamedArgument {
+                    loc: loc!(),
+                    name: id("name"),
+                    expr: expr!(value),
+                }]) => "revert my.error({name: value});",
+
+                pt::Statement::Emit(loc!(), expr!(true)) => "emit true;",
+
+                pt::Statement::Try(loc!(), expr!(true), None, vec![]) => "try true",
+                pt::Statement::Try(loc!(), expr!(true), None, vec![pt::CatchClause::Simple(loc!(), None, stmt!({}))])
+                    => "try true catch {}",
+                pt::Statement::Try(loc!(), expr!(true), Some((vec![], Box::new(stmt!({})))), vec![])
+                    => "try true returns () {}",
+                pt::Statement::Try(
+                    loc!(),
+                    expr!(true),
+                    Some((vec![], Box::new(stmt!({})))),
+                    vec![pt::CatchClause::Simple(loc!(), None, stmt!({}))]
+                ) => "try true returns () {} catch {}",
+                pt::Statement::Try(
+                    loc!(),
+                    expr!(true),
+                    Some((vec![(loc!(), Some(param!(uint256 a)))], Box::new(stmt!({})))),
+                    vec![pt::CatchClause::Simple(loc!(), None, stmt!({}))]
+                ) => "try true returns (uint256 a) {} catch {}",
+            }
+
+            pt::StorageLocation: {
+                pt::StorageLocation::Memory(loc!()) => "memory",
+                pt::StorageLocation::Storage(loc!()) => "storage",
+                pt::StorageLocation::Calldata(loc!()) => "calldata",
+            }
+
+            pt::Type: {
+                pt::Type::Address => "address",
+                pt::Type::AddressPayable => "address payable",
+                pt::Type::Payable => "payable",
+                pt::Type::Bool => "bool",
+                pt::Type::String => "string",
+                pt::Type::Int(256) => "int256",
+                pt::Type::Uint(256) => "uint256",
+                pt::Type::Bytes(32) => "bytes32",
+                pt::Type::Rational => "fixed",
+                pt::Type::DynamicBytes => "bytes",
+
+                pt::Type::Mapping {
+                    loc: loc!(),
+                    key: Box::new(expr_ty!(uint256)),
+                    key_name: None,
+                    value: Box::new(expr_ty!(uint256)),
+                    value_name: None,
+                } => "mapping(uint256 => uint256)",
+                pt::Type::Mapping {
+                    loc: loc!(),
+                    key: Box::new(expr_ty!(uint256)),
+                    key_name: Some(id("key")),
+                    value: Box::new(expr_ty!(uint256)),
+                    value_name: None,
+                } => "mapping(uint256 key => uint256)",
+                pt::Type::Mapping {
+                    loc: loc!(),
+                    key: Box::new(expr_ty!(uint256)),
+                    key_name: Some(id("key")),
+                    value: Box::new(expr_ty!(uint256)),
+                    value_name: Some(id("value")),
+                } => "mapping(uint256 key => uint256 value)",
+
+                pt::Type::Function {
+                    params: vec![],
+                    attributes: vec![],
+                    returns: None
+                } => "function ()",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![],
+                    returns: None
+                } => "function (uint256)",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256))), (loc!(), Some(param!(address)))],
+                    attributes: vec![],
+                    returns: None
+                } => "function (uint256, address)",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![pt::FunctionAttribute::Virtual(loc!())],
+                    returns: None
+                } => "function (uint256) virtual",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![pt::FunctionAttribute::Virtual(loc!()), pt::FunctionAttribute::Override(loc!(), vec![])],
+                    returns: None
+                } => "function (uint256) virtual override",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![pt::FunctionAttribute::Virtual(loc!()), pt::FunctionAttribute::Override(loc!(), vec![idp!["a", "b"]])],
+                    returns: None
+                } => "function (uint256) virtual override(a.b)",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![],
+                    returns: Some((vec![], vec![])),
+                } => "function (uint256)",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![],
+                    returns: Some((vec![(loc!(), Some(param!(uint256)))], vec![])),
+                } => "function (uint256) returns (uint256)",
+                pt::Type::Function {
+                    params: vec![(loc!(), Some(param!(uint256)))],
+                    attributes: vec![],
+                    returns: Some((vec![(loc!(), Some(param!(uint256))), (loc!(), Some(param!(address)))], vec![])),
+                } => "function (uint256) returns (uint256, address)",
+            }
+
+            pt::UserDefinedOperator: {
+                pt::UserDefinedOperator::BitwiseAnd => "&",
+                pt::UserDefinedOperator::Complement => "~",
+                pt::UserDefinedOperator::Negate => "-",
+                pt::UserDefinedOperator::BitwiseOr => "|",
+                pt::UserDefinedOperator::BitwiseXor => "^",
+                pt::UserDefinedOperator::Add => "+",
+                pt::UserDefinedOperator::Divide => "/",
+                pt::UserDefinedOperator::Modulo => "%",
+                pt::UserDefinedOperator::Multiply => "*",
+                pt::UserDefinedOperator::Subtract => "-",
+                pt::UserDefinedOperator::Equal => "==",
+                pt::UserDefinedOperator::More => ">",
+                pt::UserDefinedOperator::MoreEqual => ">=",
+                pt::UserDefinedOperator::Less => "<",
+                pt::UserDefinedOperator::LessEqual => "<=",
+                pt::UserDefinedOperator::NotEqual => "!=",
+            }
+
+            pt::UsingList: {
+                pt::UsingList::Library(idp!("id", "path")) => "id.path",
+
+                pt::UsingList::Functions(vec![]) => "{}",
+                pt::UsingList::Functions(vec![
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!["id", "path"],
+                        oper: None,
+                    },
+                    pt::UsingFunction {
+                        loc: loc!(),
+                        path: idp!["id", "path"],
+                        oper: Some(pt::UserDefinedOperator::Add),
+                }]) => "{id.path, id.path as +}",
+            }
+
+            pt::VariableAttribute: {
+                pt::VariableAttribute::Constant(loc!()) => "constant",
+                pt::VariableAttribute::Immutable(loc!()) => "immutable",
+
+                pt::VariableAttribute::Override(loc!(), vec![]) => "override",
+                pt::VariableAttribute::Override(loc!(), vec![idp!["a", "b"]]) => "override(a.b)",
+                pt::VariableAttribute::Override(loc!(), vec![idp!["a", "b"], idp!["c", "d"]])
+                    => "override(a.b, c.d)",
+            }
+
+            pt::Visibility: {
+                pt::Visibility::Public(Some(loc!())) => "public",
+                pt::Visibility::Internal(Some(loc!())) => "internal",
+                pt::Visibility::Private(Some(loc!())) => "private",
+                pt::Visibility::External(Some(loc!())) => "external",
+            }
+
+            pt::YulExpression: {
+                pt::YulExpression::BoolLiteral(loc!(), false, None) => "false",
+                pt::YulExpression::BoolLiteral(loc!(), true, None) => "true",
+                pt::YulExpression::BoolLiteral(loc!(), false, Some(id("name"))) => "false: name",
+                pt::YulExpression::BoolLiteral(loc!(), true, Some(id("name"))) => "true: name",
+
+                pt::YulExpression::NumberLiteral(loc!(), "1234".into(), "".into(), None) => "1234",
+                pt::YulExpression::NumberLiteral(loc!(), "1234".into(), "9".into(), None) => "1234e9",
+                pt::YulExpression::NumberLiteral(loc!(), "1234".into(), "".into(), Some(id("name"))) => "1234: name",
+                pt::YulExpression::NumberLiteral(loc!(), "1234".into(), "9".into(), Some(id("name"))) => "1234e9: name",
+
+                pt::YulExpression::HexNumberLiteral(loc!(), "0x1234".into(), None) => "0x1234",
+                pt::YulExpression::HexNumberLiteral(loc!(), "0x1234".into(), Some(id("name"))) => "0x1234: name",
+
+                pt::YulExpression::HexStringLiteral(lit!(hex "1234"), None) => "hex\"1234\"",
+                pt::YulExpression::HexStringLiteral(lit!(hex "1234"), Some(id("name"))) => "hex\"1234\": name",
+
+                pt::YulExpression::StringLiteral(lit!("1234"), None) => "\"1234\"",
+                pt::YulExpression::StringLiteral(lit!("1234"), Some(id("name"))) => "\"1234\": name",
+
+                pt::YulExpression::Variable(id("name")) => "name",
+
+                pt::YulExpression::FunctionCall(Box::new(pt::YulFunctionCall {
+                    loc: loc!(),
+                    id: id("name"),
+                    arguments: vec![],
+                })) => "name()",
+
+                pt::YulExpression::SuffixAccess(loc!(), Box::new(yexpr!(struct)), id("access"))
+                    => "struct.access",
+            }
+
+            pt::YulStatement: {
+                // rest tested individually
+
+                pt::YulStatement::Assign(loc!(), vec![yexpr!(var)], yexpr!(eq))
+                    => "var := eq",
+                pt::YulStatement::Assign(loc!(), vec![yexpr!(a), yexpr!(b)], yexpr!(eq))
+                    => "a, b := eq",
+
+                pt::YulStatement::VariableDeclaration(loc!(), vec![yid!(var)], None)
+                    => "let var",
+                pt::YulStatement::VariableDeclaration(loc!(), vec![yid!(a), yid!(b)], None)
+                    => "let a, b",
+                pt::YulStatement::VariableDeclaration(loc!(), vec![yid!(var)], Some(yexpr!(eq)))
+                    => "let var := eq",
+                pt::YulStatement::VariableDeclaration(loc!(), vec![yid!(a), yid!(b)], Some(yexpr!(eq)))
+                    => "let a, b := eq",
+
+                pt::YulStatement::If(loc!(), yexpr!(expr), yul_block()) => "if expr {}",
+
+                pt::YulStatement::Leave(loc!()) => "leave",
+                pt::YulStatement::Break(loc!()) => "break",
+                pt::YulStatement::Continue(loc!()) => "continue",
+            }
+
+            pt::YulSwitchOptions: {
+                pt::YulSwitchOptions::Case(loc!(), yexpr!(expr), yul_block()) => "case expr {}",
+                pt::YulSwitchOptions::Default(loc!(), yul_block()) => "default {}",
+            }
+        ];
+    }
+}

+ 482 - 0
solang-parser/src/helpers/loc.rs

@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lexer::LexicalError;
+use crate::pt::{self, Loc};
+use std::sync::Arc;
+use std::{borrow::Cow, rc::Rc};
+
+/// Returns the optional code location.
+pub trait OptionalCodeLocation {
+    /// Returns the optional code location of `self`.
+    fn loc_opt(&self) -> Option<Loc>;
+}
+
+impl<T: CodeLocation> OptionalCodeLocation for Option<T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        self.as_ref().map(CodeLocation::loc)
+    }
+}
+
+impl OptionalCodeLocation for pt::Visibility {
+    fn loc_opt(&self) -> Option<Loc> {
+        match self {
+            Self::Internal(l, ..)
+            | Self::External(l, ..)
+            | Self::Private(l, ..)
+            | Self::Public(l, ..) => *l,
+        }
+    }
+}
+
+impl OptionalCodeLocation for pt::SourceUnit {
+    #[inline]
+    fn loc_opt(&self) -> Option<Loc> {
+        self.0.loc_opt()
+    }
+}
+
+impl<T: CodeLocation> OptionalCodeLocation for [T] {
+    // TODO: Merge first with last span?
+    fn loc_opt(&self) -> Option<Loc> {
+        self.first().map(CodeLocation::loc)
+    }
+}
+
+impl<T: CodeLocation> OptionalCodeLocation for Vec<T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<'a, T: ?Sized + OptionalCodeLocation> OptionalCodeLocation for &'a T {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<'a, T: ?Sized + OptionalCodeLocation> OptionalCodeLocation for &'a mut T {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<'a, T: ?Sized + ToOwned + OptionalCodeLocation> OptionalCodeLocation for Cow<'a, T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<T: ?Sized + OptionalCodeLocation> OptionalCodeLocation for Box<T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<T: ?Sized + OptionalCodeLocation> OptionalCodeLocation for Rc<T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+impl<T: ?Sized + OptionalCodeLocation> OptionalCodeLocation for Arc<T> {
+    fn loc_opt(&self) -> Option<Loc> {
+        (**self).loc_opt()
+    }
+}
+
+// would be: `impl<T: CodeLocation> OptionalCodeLocation for T { ... }`
+// but then we wouldn't have the correct implementation for `Box<T>` and the other smart pointers
+macro_rules! impl_optional_for_pt {
+    ($($t:ty),+ $(,)?) => {
+        $(
+            impl OptionalCodeLocation for $t {
+                #[inline]
+                fn loc_opt(&self) -> Option<Loc> {
+                    Some(<$t as CodeLocation>::loc(self))
+                }
+            }
+        )+
+    };
+}
+
+impl_optional_for_pt!(
+    // structs
+    pt::Annotation,
+    pt::Base,
+    pt::ContractDefinition,
+    pt::EnumDefinition,
+    pt::ErrorDefinition,
+    pt::ErrorParameter,
+    pt::EventDefinition,
+    pt::EventParameter,
+    pt::FunctionDefinition,
+    pt::HexLiteral,
+    pt::Identifier,
+    pt::IdentifierPath,
+    pt::NamedArgument,
+    pt::Parameter,
+    pt::StringLiteral,
+    pt::StructDefinition,
+    pt::TypeDefinition,
+    pt::Using,
+    pt::UsingFunction,
+    pt::VariableDeclaration,
+    pt::VariableDefinition,
+    pt::YulBlock,
+    pt::YulFor,
+    pt::YulFunctionCall,
+    pt::YulFunctionDefinition,
+    pt::YulSwitch,
+    pt::YulTypedIdentifier,
+    // enums
+    pt::CatchClause,
+    pt::Comment,
+    pt::ContractPart,
+    pt::ContractTy,
+    pt::Expression,
+    pt::FunctionAttribute,
+    pt::Import,
+    pt::Loc,
+    pt::Mutability,
+    pt::SourceUnitPart,
+    pt::Statement,
+    pt::StorageLocation,
+    pt::UsingList,
+    pt::VariableAttribute,
+    pt::YulExpression,
+    pt::YulStatement,
+    pt::YulSwitchOptions,
+    // other
+    LexicalError,
+);
+
+/// Returns the code location.
+pub trait CodeLocation {
+    /// Returns the code location of `self`.
+    fn loc(&self) -> Loc;
+}
+
+impl CodeLocation for Loc {
+    #[inline]
+    fn loc(&self) -> Loc {
+        *self
+    }
+}
+
+impl<'a, T: ?Sized + CodeLocation> CodeLocation for &'a T {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+impl<'a, T: ?Sized + CodeLocation> CodeLocation for &'a mut T {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+impl<'a, T: ?Sized + ToOwned + CodeLocation> CodeLocation for Cow<'a, T> {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+impl<T: ?Sized + CodeLocation> CodeLocation for Box<T> {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+impl<T: ?Sized + CodeLocation> CodeLocation for Rc<T> {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+impl<T: ?Sized + CodeLocation> CodeLocation for Arc<T> {
+    fn loc(&self) -> Loc {
+        (**self).loc()
+    }
+}
+
+macro_rules! impl_for_structs {
+    ($($t:ty),+ $(,)?) => {
+        $(
+            impl CodeLocation for $t {
+                #[inline]
+                fn loc(&self) -> Loc {
+                    self.loc
+                }
+            }
+        )+
+    };
+}
+
+// all structs except for SourceUnit
+impl_for_structs!(
+    pt::Annotation,
+    pt::Base,
+    pt::ContractDefinition,
+    pt::EnumDefinition,
+    pt::ErrorDefinition,
+    pt::ErrorParameter,
+    pt::EventDefinition,
+    pt::EventParameter,
+    pt::FunctionDefinition,
+    pt::HexLiteral,
+    pt::Identifier,
+    pt::IdentifierPath,
+    pt::NamedArgument,
+    pt::Parameter,
+    pt::StringLiteral,
+    pt::StructDefinition,
+    pt::TypeDefinition,
+    pt::Using,
+    pt::UsingFunction,
+    pt::VariableDeclaration,
+    pt::VariableDefinition,
+    pt::YulBlock,
+    pt::YulFor,
+    pt::YulFunctionCall,
+    pt::YulFunctionDefinition,
+    pt::YulSwitch,
+    pt::YulTypedIdentifier,
+);
+
+macro_rules! impl_for_enums {
+    ($(
+        $t:ty: match $s:ident {
+            $($m:tt)*
+        }
+    )+) => {
+        $(
+            impl CodeLocation for $t {
+                fn loc(&$s) -> Loc {
+                    match *$s {
+                        $($m)*
+                    }
+                }
+            }
+        )+
+    };
+}
+
+// all enums except for Type, UserDefinedOperator and FunctionTy
+impl_for_enums! {
+    pt::CatchClause: match self {
+        Self::Simple(l, ..)
+        | Self::Named(l, ..) => l,
+    }
+
+    pt::Comment: match self {
+        Self::Line(l, ..)
+        | Self::Block(l, ..)
+        | Self::DocLine(l, ..)
+        | Self::DocBlock(l, ..) => l,
+    }
+
+    pt::ContractPart: match self {
+        Self::StructDefinition(ref l, ..) => l.loc(),
+        Self::EventDefinition(ref l, ..) => l.loc(),
+        Self::EnumDefinition(ref l, ..) => l.loc(),
+        Self::ErrorDefinition(ref l, ..) => l.loc(),
+        Self::VariableDefinition(ref l, ..) => l.loc(),
+        Self::FunctionDefinition(ref l, ..) => l.loc(),
+        Self::TypeDefinition(ref l, ..) => l.loc(),
+        Self::Annotation(ref l, ..) => l.loc(),
+        Self::Using(ref l, ..) => l.loc(),
+        Self::StraySemicolon(l, ..) => l,
+    }
+
+    pt::ContractTy: match self {
+        Self::Abstract(l, ..)
+        | Self::Contract(l, ..)
+        | Self::Library(l, ..)
+        | Self::Interface(l, ..) => l,
+    }
+
+    pt::Expression: match self {
+        // literals have at least one item
+        Self::StringLiteral(ref l, ..) => l.loc_opt().unwrap(),
+        Self::HexLiteral(ref l, ..) => l.loc_opt().unwrap(),
+        Self::Variable(ref l, ..) => l.loc(),
+        Self::PostIncrement(l, ..)
+        | Self::PostDecrement(l, ..)
+        | Self::New(l, ..)
+        | Self::Parenthesis(l, ..)
+        | Self::ArraySubscript(l, ..)
+        | Self::ArraySlice(l, ..)
+        | Self::MemberAccess(l, ..)
+        | Self::FunctionCall(l, ..)
+        | Self::FunctionCallBlock(l, ..)
+        | Self::NamedFunctionCall(l, ..)
+        | Self::Not(l, ..)
+        | Self::Complement(l, ..)
+        | Self::Delete(l, ..)
+        | Self::PreIncrement(l, ..)
+        | Self::PreDecrement(l, ..)
+        | Self::UnaryPlus(l, ..)
+        | Self::Negate(l, ..)
+        | Self::Power(l, ..)
+        | Self::Multiply(l, ..)
+        | Self::Divide(l, ..)
+        | Self::Modulo(l, ..)
+        | Self::Add(l, ..)
+        | Self::Subtract(l, ..)
+        | Self::ShiftLeft(l, ..)
+        | Self::ShiftRight(l, ..)
+        | Self::BitwiseAnd(l, ..)
+        | Self::BitwiseXor(l, ..)
+        | Self::BitwiseOr(l, ..)
+        | Self::Less(l, ..)
+        | Self::More(l, ..)
+        | Self::LessEqual(l, ..)
+        | Self::MoreEqual(l, ..)
+        | Self::Equal(l, ..)
+        | Self::NotEqual(l, ..)
+        | Self::And(l, ..)
+        | Self::Or(l, ..)
+        | Self::ConditionalOperator(l, ..)
+        | Self::Assign(l, ..)
+        | Self::AssignOr(l, ..)
+        | Self::AssignAnd(l, ..)
+        | Self::AssignXor(l, ..)
+        | Self::AssignShiftLeft(l, ..)
+        | Self::AssignShiftRight(l, ..)
+        | Self::AssignAdd(l, ..)
+        | Self::AssignSubtract(l, ..)
+        | Self::AssignMultiply(l, ..)
+        | Self::AssignDivide(l, ..)
+        | Self::AssignModulo(l, ..)
+        | Self::BoolLiteral(l, ..)
+        | Self::NumberLiteral(l, ..)
+        | Self::RationalNumberLiteral(l, ..)
+        | Self::HexNumberLiteral(l, ..)
+        | Self::ArrayLiteral(l, ..)
+        | Self::List(l, ..)
+        | Self::Type(l, ..)
+        | Self::This(l, ..)
+        | Self::AddressLiteral(l, ..) => l,
+    }
+
+    pt::FunctionAttribute: match self {
+        Self::Mutability(ref l) => l.loc(),
+        Self::Visibility(ref l) => l.loc_opt().unwrap_or_default(),
+        Self::Virtual(l, ..)
+        | Self::Immutable(l, ..)
+        | Self::Override(l, ..,)
+        | Self::BaseOrModifier(l, ..)
+        | Self::Error(l, ..) => l,
+    }
+
+    pt::Import: match self {
+        Self::GlobalSymbol(.., l)
+        | Self::Plain(.., l)
+        | Self::Rename(.., l) => l,
+    }
+
+    pt::Mutability: match self {
+        Self::Constant(l, ..)
+        | Self::Payable(l, ..)
+        | Self::Pure(l, ..)
+        | Self::View(l, ..) => l,
+    }
+
+    pt::SourceUnitPart: match self {
+        Self::ImportDirective(ref l, ..) => l.loc(),
+        Self::ContractDefinition(ref l, ..) => l.loc(),
+        Self::EnumDefinition(ref l, ..) => l.loc(),
+        Self::StructDefinition(ref l, ..) => l.loc(),
+        Self::EventDefinition(ref l, ..) => l.loc(),
+        Self::ErrorDefinition(ref l, ..) => l.loc(),
+        Self::FunctionDefinition(ref l, ..) => l.loc(),
+        Self::VariableDefinition(ref l, ..) => l.loc(),
+        Self::TypeDefinition(ref l, ..) => l.loc(),
+        Self::Annotation(ref l, ..) => l.loc(),
+        Self::Using(ref l, ..) => l.loc(),
+        Self::PragmaDirective(l, ..)
+        | Self::StraySemicolon(l, ..) => l,
+    }
+
+    pt::Statement: match self {
+        Self::Block { loc: l, .. }
+        | Self::Assembly { loc: l, .. }
+        | Self::Args(l, ..)
+        | Self::If(l, ..)
+        | Self::While(l, ..)
+        | Self::Expression(l, ..)
+        | Self::VariableDefinition(l, ..)
+        | Self::For(l, ..)
+        | Self::DoWhile(l, ..)
+        | Self::Continue(l, ..)
+        | Self::Break(l, ..)
+        | Self::Return(l, ..)
+        | Self::Revert(l, ..)
+        | Self::RevertNamedArgs(l, ..)
+        | Self::Emit(l, ..)
+        | Self::Try(l, ..)
+        | Self::Error(l, ..) => l,
+    }
+
+    pt::StorageLocation: match self {
+        Self::Calldata(l, ..)
+        | Self::Memory(l, ..)
+        | Self::Storage(l, ..) => l,
+    }
+
+    pt::UsingList: match self {
+        Self::Library(ref l, ..) => l.loc(),
+        Self::Functions(ref l, ..) => l.loc_opt().unwrap_or_default(),
+        Self::Error => panic!("an error occurred"),
+    }
+
+    pt::VariableAttribute: match self {
+        Self::Visibility(ref l, ..) => l.loc_opt().unwrap_or_default(),
+        Self::Constant(l, ..)
+        | Self::Immutable(l, ..)
+        | Self::Override(l, ..) => l,
+    }
+
+    pt::YulExpression: match self {
+        Self::HexStringLiteral(ref l, ..) => l.loc(),
+        Self::StringLiteral(ref l, ..) => l.loc(),
+        Self::Variable(ref l, ..) => l.loc(),
+        Self::FunctionCall(ref l, ..) => l.loc(),
+        Self::BoolLiteral(l, ..)
+        | Self::NumberLiteral(l, ..)
+        | Self::HexNumberLiteral(l, ..)
+        | Self::SuffixAccess(l, ..) => l,
+    }
+
+    pt::YulStatement: match self {
+        Self::Block(ref l, ..) => l.loc(),
+        Self::FunctionDefinition(ref l, ..) => l.loc(),
+        Self::FunctionCall(ref l, ..) => l.loc(),
+        Self::For(ref l, ..) => l.loc(),
+        Self::Switch(ref l, ..) => l.loc(),
+        Self::Assign(l, ..)
+        | Self::VariableDeclaration(l, ..)
+        | Self::If(l, ..)
+        | Self::Leave(l, ..)
+        | Self::Break(l, ..)
+        | Self::Continue(l, ..)
+        | Self::Error(l, ..) => l,
+    }
+
+    pt::YulSwitchOptions: match self {
+        Self::Case(l, ..)
+        | Self::Default(l, ..) => l,
+    }
+
+    // other
+    LexicalError: match self {
+        Self::EndOfFileInComment(l)
+        | Self::EndOfFileInString(l)
+        | Self::EndofFileInHex(l)
+        | Self::MissingNumber(l)
+        | Self::InvalidCharacterInHexLiteral(l, _)
+        | Self::UnrecognisedToken(l, _)
+        | Self::ExpectedFrom(l, _)
+        | Self::MissingExponent(l) => l,
+    }
+}

+ 10 - 0
solang-parser/src/helpers/mod.rs

@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Helper functions and traits for parse tree types.
+
+mod fmt;
+
+mod loc;
+pub use loc::{CodeLocation, OptionalCodeLocation};
+
+mod ord;

+ 43 - 0
solang-parser/src/helpers/ord.rs

@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Implements `PartialOrd` and `Ord` for some parse tree data types, following the
+//! [Solidity style guide][ref].
+//!
+//! [ref]: https://docs.soliditylang.org/en/latest/style-guide.html
+
+use crate::pt;
+use std::cmp::Ordering;
+
+macro_rules! impl_with_cast {
+    ($($t:ty),+) => {
+        $(
+            impl $t {
+                #[inline]
+                const fn as_discriminant<'a>(&'a self) -> &'a u8 {
+                    // SAFETY: See <https://doc.rust-lang.org/stable/std/mem/fn.discriminant.html#accessing-the-numeric-value-of-the-discriminant>
+                    // and <https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting>
+                    //
+                    // `$t` must be `repr(u8)` for this to be safe.
+                    unsafe { &*(self as *const Self as *const u8) }
+                }
+            }
+
+            impl PartialOrd for $t {
+                #[inline]
+                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+                    PartialOrd::partial_cmp(self.as_discriminant(), other.as_discriminant())
+                }
+            }
+
+            impl Ord for $t {
+                #[inline]
+                fn cmp(&self, other: &Self) -> Ordering {
+                    Ord::cmp(self.as_discriminant(), other.as_discriminant())
+                }
+            }
+        )+
+    };
+}
+
+// SAFETY: every type must be `repr(u8)` for this to be safe, see comments in macro implementation.
+impl_with_cast!(pt::Visibility, pt::VariableAttribute, pt::FunctionAttribute);

+ 703 - 512
solang-parser/src/lexer.rs

@@ -1,26 +1,36 @@
 // SPDX-License-Identifier: Apache-2.0
 
-//
-// Solidity custom lexer. Solidity needs a custom lexer for two reasons:
-//  - comments and doc comments
-//  - pragma value is [^;]+
-//
+//! Custom Solidity lexer.
+//!
+//! Solidity needs a custom lexer for two reasons:
+//!  - comments and doc comments
+//!  - pragma value is [^;]+
+
+use crate::pt::{Comment, Loc};
 use itertools::{peek_nth, PeekNth};
 use phf::phf_map;
 use std::{fmt, str::CharIndices};
+use thiserror::Error;
 use unicode_xid::UnicodeXID;
 
-use crate::pt::{CodeLocation, Comment, Loc};
+/// A spanned [Token].
+pub type Spanned<'a> = (usize, Token<'a>, usize);
 
-pub type Spanned<Token, Loc, Error> = Result<(Loc, Token, Loc), Error>;
+/// [Lexer]'s Result type.
+pub type Result<'a, T = Spanned<'a>, E = LexicalError> = std::result::Result<T, E>;
 
+/// A Solidity lexical token. Produced by [Lexer].
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[allow(missing_docs)]
 pub enum Token<'input> {
     Identifier(&'input str),
+    /// `(unicode, literal)`
     StringLiteral(bool, &'input str),
     AddressLiteral(&'input str),
     HexLiteral(&'input str),
+    /// `(number, exponent)`
     Number(&'input str, &'input str),
+    /// `(number, fraction, exponent)`
     RationalNumber(&'input str, &'input str, &'input str),
     HexNumber(&'input str),
     Divide,
@@ -311,64 +321,69 @@ impl<'input> fmt::Display for Token<'input> {
     }
 }
 
+/// Custom Solidity lexer.
+///
+/// # Examples
+///
+/// ```
+/// use solang_parser::lexer::{Lexer, Token};
+///
+/// let source = "uint256 number = 0;";
+/// let mut comments = Vec::new();
+/// let mut errors = Vec::new();
+/// let mut lexer = Lexer::new(source, 0, &mut comments, &mut errors);
+///
+/// let mut next_token = || lexer.next().map(|result| result.map(|(_, token, _)| token));
+/// assert_eq!(next_token(), Some(Ok(Token::Uint(256))));
+/// assert_eq!(next_token(), Some(Ok(Token::Identifier("number"))));
+/// assert_eq!(next_token(), Some(Ok(Token::Assign)));
+/// assert_eq!(next_token(), Some(Ok(Token::Number("0", ""))));
+/// assert_eq!(next_token(), Some(Ok(Token::Semicolon)));
+/// assert_eq!(next_token(), None);
+/// assert!(errors.is_empty());
+/// assert!(comments.is_empty());
+/// ```
+#[derive(Debug)]
 pub struct Lexer<'input> {
     input: &'input str,
     chars: PeekNth<CharIndices<'input>>,
     comments: &'input mut Vec<Comment>,
     file_no: usize,
     last_tokens: [Option<Token<'input>>; 2],
+    /// The mutable reference to the error vector.
     pub errors: &'input mut Vec<LexicalError>,
 }
 
-#[derive(Debug, PartialEq, Eq, Clone)]
+/// An error thrown by [Lexer].
+#[derive(Debug, Clone, PartialEq, Eq, Error)]
+#[allow(missing_docs)]
 pub enum LexicalError {
+    #[error("end of file found in comment")]
     EndOfFileInComment(Loc),
+
+    #[error("end of file found in string literal")]
     EndOfFileInString(Loc),
+
+    #[error("end of file found in hex literal string")]
     EndofFileInHex(Loc),
+
+    #[error("missing number")]
     MissingNumber(Loc),
+
+    #[error("invalid character '{1}' in hex literal string")]
     InvalidCharacterInHexLiteral(Loc, char),
+
+    #[error("unrecognised token '{1}'")]
     UnrecognisedToken(Loc, String),
-    MissingExponent(Loc),
-    ExpectedFrom(Loc, String),
-}
 
-impl fmt::Display for LexicalError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            LexicalError::EndOfFileInComment(..) => write!(f, "end of file found in comment"),
-            LexicalError::EndOfFileInString(..) => {
-                write!(f, "end of file found in string literal")
-            }
-            LexicalError::EndofFileInHex(..) => {
-                write!(f, "end of file found in hex literal string")
-            }
-            LexicalError::MissingNumber(..) => write!(f, "missing number"),
-            LexicalError::InvalidCharacterInHexLiteral(_, ch) => {
-                write!(f, "invalid character '{ch}' in hex literal string")
-            }
-            LexicalError::UnrecognisedToken(_, t) => write!(f, "unrecognised token '{t}'"),
-            LexicalError::ExpectedFrom(_, t) => write!(f, "'{t}' found where 'from' expected"),
-            LexicalError::MissingExponent(..) => write!(f, "missing number"),
-        }
-    }
-}
+    #[error("missing exponent")]
+    MissingExponent(Loc),
 
-impl CodeLocation for LexicalError {
-    fn loc(&self) -> Loc {
-        match self {
-            LexicalError::EndOfFileInComment(loc, ..)
-            | LexicalError::EndOfFileInString(loc, ..)
-            | LexicalError::EndofFileInHex(loc, ..)
-            | LexicalError::MissingNumber(loc, ..)
-            | LexicalError::InvalidCharacterInHexLiteral(loc, _)
-            | LexicalError::UnrecognisedToken(loc, ..)
-            | LexicalError::ExpectedFrom(loc, ..)
-            | LexicalError::MissingExponent(loc, ..) => *loc,
-        }
-    }
+    #[error("'{1}' found where 'from' expected")]
+    ExpectedFrom(Loc, String),
 }
 
-/// Is this word a keyword in Solidity
+/// Returns whether `word` is a keyword in Solidity.
 pub fn is_keyword(word: &str) -> bool {
     KEYWORDS.contains_key(word)
 }
@@ -541,6 +556,18 @@ static KEYWORDS: phf::Map<&'static str, Token> = phf_map! {
 };
 
 impl<'input> Lexer<'input> {
+    /// Instantiates a new Lexer.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use solang_parser::lexer::Lexer;
+    ///
+    /// let source = "uint256 number = 0;";
+    /// let mut comments = Vec::new();
+    /// let mut errors = Vec::new();
+    /// let mut lexer = Lexer::new(source, 0, &mut comments, &mut errors);
+    /// ```
     pub fn new(
         input: &'input str,
         file_no: usize,
@@ -557,11 +584,7 @@ impl<'input> Lexer<'input> {
         }
     }
 
-    fn parse_number(
-        &mut self,
-        mut start: usize,
-        ch: char,
-    ) -> Result<(usize, Token<'input>, usize), LexicalError> {
+    fn parse_number(&mut self, mut start: usize, ch: char) -> Result<'input> {
         let mut is_rational = false;
         if ch == '0' {
             if let Some((_, 'x')) = self.chars.peek() {
@@ -689,7 +712,7 @@ impl<'input> Lexer<'input> {
         token_start: usize,
         string_start: usize,
         quote_char: char,
-    ) -> Result<(usize, Token<'input>, usize), LexicalError> {
+    ) -> Result<'input> {
         let mut end;
 
         let mut last_was_escape = false;
@@ -721,7 +744,7 @@ impl<'input> Lexer<'input> {
         ))
     }
 
-    fn next(&mut self) -> Option<Result<(usize, Token<'input>, usize), LexicalError>> {
+    fn next(&mut self) -> Option<Result<'input>> {
         'toplevel: loop {
             match self.chars.next() {
                 Some((start, ch)) if ch == '_' || ch == '$' || UnicodeXID::is_xid_start(ch) => {
@@ -1155,7 +1178,7 @@ impl<'input> Lexer<'input> {
     }
 
     /// Next token is pragma value. Return it
-    fn pragma_value(&mut self) -> Option<Result<(usize, Token<'input>, usize), LexicalError>> {
+    fn pragma_value(&mut self) -> Option<Result<'input>> {
         // special parser for pragma solidity >=0.4.22 <0.7.0;
         let mut start = None;
         let mut end = 0;
@@ -1196,9 +1219,8 @@ impl<'input> Lexer<'input> {
 }
 
 impl<'input> Iterator for Lexer<'input> {
-    type Item = Spanned<Token<'input>, usize, LexicalError>;
+    type Item = Result<'input>;
 
-    /// Return the next token
     fn next(&mut self) -> Option<Self::Item> {
         // Lexer should be aware of whether the last two tokens were
         // pragma followed by identifier. If this is true, then special parsing should be
@@ -1221,506 +1243,675 @@ impl<'input> Iterator for Lexer<'input> {
     }
 }
 
-#[test]
-fn lexertest() {
-    let mut comments = Vec::new();
-    let mut errors = Vec::new();
-
-    let multiple_errors = r#" 9ea -9e € bool hex uint8 hex"g"   /**  "#;
-    let tokens = Lexer::new(multiple_errors, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-    assert_eq!(
-        tokens,
-        vec![
-            Ok((3, Token::Identifier("a"), 4)),
-            Ok((5, Token::Subtract, 6)),
-            Ok((13, Token::Bool, 17)),
-            Ok((18, Token::Identifier("hex"), 21)),
-            Ok((22, Token::Uint(8), 27)),
-        ]
-    );
-
-    assert_eq!(
-        errors,
-        vec![
-            LexicalError::MissingExponent(Loc::File(0, 1, 42)),
-            LexicalError::MissingExponent(Loc::File(0, 6, 42)),
-            LexicalError::UnrecognisedToken(Loc::File(0, 9, 12), '€'.to_string()),
-            LexicalError::InvalidCharacterInHexLiteral(Loc::File(0, 32, 33), 'g'),
-            LexicalError::EndOfFileInComment(Loc::File(0, 37, 42)),
-        ]
-    );
-
-    let mut errors = Vec::new();
-    let tokens = Lexer::new("bool", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(tokens, vec!(Ok((0, Token::Bool, 4))));
-
-    let tokens = Lexer::new("uint8", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_lexer() {
+        let mut comments = Vec::new();
+        let mut errors = Vec::new();
+
+        let multiple_errors = r#" 9ea -9e € bool hex uint8 hex"g"   /**  "#;
+        let tokens = Lexer::new(multiple_errors, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+        assert_eq!(
+            tokens,
+            vec![
+                Ok((3, Token::Identifier("a"), 4)),
+                Ok((5, Token::Subtract, 6)),
+                Ok((13, Token::Bool, 17)),
+                Ok((18, Token::Identifier("hex"), 21)),
+                Ok((22, Token::Uint(8), 27)),
+            ]
+        );
+
+        assert_eq!(
+            errors,
+            vec![
+                LexicalError::MissingExponent(Loc::File(0, 1, 42)),
+                LexicalError::MissingExponent(Loc::File(0, 6, 42)),
+                LexicalError::UnrecognisedToken(Loc::File(0, 9, 12), '€'.to_string()),
+                LexicalError::InvalidCharacterInHexLiteral(Loc::File(0, 32, 33), 'g'),
+                LexicalError::EndOfFileInComment(Loc::File(0, 37, 42)),
+            ]
+        );
 
-    assert_eq!(tokens, vec!(Ok((0, Token::Uint(8), 5))));
+        let mut errors = Vec::new();
+        let tokens = Lexer::new("bool", 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    let tokens = Lexer::new("hex", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        assert_eq!(tokens, vec!(Ok((0, Token::Bool, 4))));
 
-    assert_eq!(tokens, vec!(Ok((0, Token::Identifier("hex"), 3))));
-
-    let tokens = Lexer::new(
-        "hex\"cafe_dead\" /* adad*** */",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(Ok((0, Token::HexLiteral("hex\"cafe_dead\""), 14)))
-    );
-
-    let tokens = Lexer::new(
-        "// foo bar\n0x00fead0_12 00090 0_0",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
-            Ok((24, Token::Number("00090", ""), 29)),
-            Ok((30, Token::Number("0_0", ""), 33))
-        )
-    );
-
-    let tokens = Lexer::new(
-        "// foo bar\n0x00fead0_12 9.0008 0_0",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
-            Ok((24, Token::RationalNumber("9", "0008", ""), 30)),
-            Ok((31, Token::Number("0_0", ""), 34))
-        )
-    );
-
-    let tokens = Lexer::new(
-        "// foo bar\n0x00fead0_12 .0008 0.9e2",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
-            Ok((24, Token::RationalNumber("", "0008", ""), 29)),
-            Ok((30, Token::RationalNumber("0", "9", "2"), 35))
-        )
-    );
-
-    let tokens = Lexer::new(
-        "// foo bar\n0x00fead0_12 .0008 0.9e-2-2",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
-            Ok((24, Token::RationalNumber("", "0008", ""), 29)),
-            Ok((30, Token::RationalNumber("0", "9", "-2"), 36)),
-            Ok((36, Token::Subtract, 37)),
-            Ok((37, Token::Number("2", ""), 38))
-        )
-    );
+        let tokens = Lexer::new("uint8", 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    let tokens = Lexer::new("1.2_3e2-", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        assert_eq!(tokens, vec!(Ok((0, Token::Uint(8), 5))));
 
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::RationalNumber("1", "2_3", "2"), 7)),
-            Ok((7, Token::Subtract, 8))
-        )
-    );
+        let tokens = Lexer::new("hex", 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    let tokens = Lexer::new("\"foo\"", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        assert_eq!(tokens, vec!(Ok((0, Token::Identifier("hex"), 3))));
 
-    assert_eq!(
-        tokens,
-        vec!(Ok((0, Token::StringLiteral(false, "foo"), 5)),)
-    );
-
-    let tokens = Lexer::new(
-        "pragma solidity >=0.5.0 <0.7.0;",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Pragma, 6)),
-            Ok((7, Token::Identifier("solidity"), 15)),
-            Ok((16, Token::StringLiteral(false, ">=0.5.0 <0.7.0"), 30)),
-            Ok((30, Token::Semicolon, 31)),
+        let tokens = Lexer::new(
+            "hex\"cafe_dead\" /* adad*** */",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new(
-        "pragma solidity \t>=0.5.0 <0.7.0 \n ;",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Pragma, 6)),
-            Ok((7, Token::Identifier("solidity"), 15)),
-            Ok((17, Token::StringLiteral(false, ">=0.5.0 <0.7.0"), 31)),
-            Ok((34, Token::Semicolon, 35)),
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::HexLiteral("hex\"cafe_dead\""), 14)))
+        );
+
+        let tokens = Lexer::new(
+            "// foo bar\n0x00fead0_12 00090 0_0",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new("pragma solidity 赤;", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Pragma, 6)),
-            Ok((7, Token::Identifier("solidity"), 15)),
-            Ok((16, Token::StringLiteral(false, "赤"), 19)),
-            Ok((19, Token::Semicolon, 20))
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
+                Ok((24, Token::Number("00090", ""), 29)),
+                Ok((30, Token::Number("0_0", ""), 33))
+            )
+        );
+
+        let tokens = Lexer::new(
+            "// foo bar\n0x00fead0_12 9.0008 0_0",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new(">>= >> >= >", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::ShiftRightAssign, 3)),
-            Ok((4, Token::ShiftRight, 6)),
-            Ok((7, Token::MoreEqual, 9)),
-            Ok((10, Token::More, 11)),
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
+                Ok((24, Token::RationalNumber("9", "0008", ""), 30)),
+                Ok((31, Token::Number("0_0", ""), 34))
+            )
+        );
+
+        let tokens = Lexer::new(
+            "// foo bar\n0x00fead0_12 .0008 0.9e2",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new("<<= << <= <", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::ShiftLeftAssign, 3)),
-            Ok((4, Token::ShiftLeft, 6)),
-            Ok((7, Token::LessEqual, 9)),
-            Ok((10, Token::Less, 11)),
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
+                Ok((24, Token::RationalNumber("", "0008", ""), 29)),
+                Ok((30, Token::RationalNumber("0", "9", "2"), 35))
+            )
+        );
+
+        let tokens = Lexer::new(
+            "// foo bar\n0x00fead0_12 .0008 0.9e-2-2",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new("-16 -- - -=", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Subtract, 1)),
-            Ok((1, Token::Number("16", ""), 3)),
-            Ok((4, Token::Decrement, 6)),
-            Ok((7, Token::Subtract, 8)),
-            Ok((9, Token::SubtractAssign, 11)),
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((11, Token::HexNumber("0x00fead0_12"), 23)),
+                Ok((24, Token::RationalNumber("", "0008", ""), 29)),
+                Ok((30, Token::RationalNumber("0", "9", "-2"), 36)),
+                Ok((36, Token::Subtract, 37)),
+                Ok((37, Token::Number("2", ""), 38))
+            )
+        );
+
+        let tokens = Lexer::new("1.2_3e2-", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::RationalNumber("1", "2_3", "2"), 7)),
+                Ok((7, Token::Subtract, 8))
+            )
+        );
+
+        let tokens = Lexer::new("\"foo\"", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::StringLiteral(false, "foo"), 5)),)
+        );
+
+        let tokens = Lexer::new(
+            "pragma solidity >=0.5.0 <0.7.0;",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new("-4 ", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Subtract, 1)),
-            Ok((1, Token::Number("4", ""), 2)),
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Pragma, 6)),
+                Ok((7, Token::Identifier("solidity"), 15)),
+                Ok((16, Token::StringLiteral(false, ">=0.5.0 <0.7.0"), 30)),
+                Ok((30, Token::Semicolon, 31)),
+            )
+        );
+
+        let tokens = Lexer::new(
+            "pragma solidity \t>=0.5.0 <0.7.0 \n ;",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let mut errors = Vec::new();
-    let _ = Lexer::new(r#"hex"abcdefg""#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        errors,
-        vec![LexicalError::InvalidCharacterInHexLiteral(
-            Loc::File(0, 10, 11),
-            'g'
-        )]
-    );
-
-    let mut errors = Vec::new();
-    let _ = Lexer::new(r#" € "#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        errors,
-        vec!(LexicalError::UnrecognisedToken(
-            Loc::File(0, 1, 4),
-            "€".to_owned()
-        ))
-    );
-
-    let mut errors = Vec::new();
-    let _ = Lexer::new(r#"€"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        errors,
-        vec!(LexicalError::UnrecognisedToken(
-            Loc::File(0, 0, 3),
-            "€".to_owned()
-        ))
-    );
-
-    let tokens = Lexer::new(r#"pragma foo bar"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Pragma, 6)),
+                Ok((7, Token::Identifier("solidity"), 15)),
+                Ok((17, Token::StringLiteral(false, ">=0.5.0 <0.7.0"), 31)),
+                Ok((34, Token::Semicolon, 35)),
+            )
+        );
+
+        let tokens =
+            Lexer::new("pragma solidity 赤;", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Pragma, 6)),
+                Ok((7, Token::Identifier("solidity"), 15)),
+                Ok((16, Token::StringLiteral(false, "赤"), 19)),
+                Ok((19, Token::Semicolon, 20))
+            )
+        );
+
+        let tokens = Lexer::new(">>= >> >= >", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::ShiftRightAssign, 3)),
+                Ok((4, Token::ShiftRight, 6)),
+                Ok((7, Token::MoreEqual, 9)),
+                Ok((10, Token::More, 11)),
+            )
+        );
+
+        let tokens = Lexer::new("<<= << <= <", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::ShiftLeftAssign, 3)),
+                Ok((4, Token::ShiftLeft, 6)),
+                Ok((7, Token::LessEqual, 9)),
+                Ok((10, Token::Less, 11)),
+            )
+        );
+
+        let tokens = Lexer::new("-16 -- - -=", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Subtract, 1)),
+                Ok((1, Token::Number("16", ""), 3)),
+                Ok((4, Token::Decrement, 6)),
+                Ok((7, Token::Subtract, 8)),
+                Ok((9, Token::SubtractAssign, 11)),
+            )
+        );
+
+        let tokens = Lexer::new("-4 ", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Subtract, 1)),
+                Ok((1, Token::Number("4", ""), 2)),
+            )
+        );
+
+        let mut errors = Vec::new();
+        let _ = Lexer::new(r#"hex"abcdefg""#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            errors,
+            vec![LexicalError::InvalidCharacterInHexLiteral(
+                Loc::File(0, 10, 11),
+                'g'
+            )]
+        );
 
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Pragma, 6)),
-            Ok((7, Token::Identifier("foo"), 10)),
-            Ok((11, Token::StringLiteral(false, "bar"), 14)),
-        )
-    );
+        let mut errors = Vec::new();
+        let _ = Lexer::new(r#" € "#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    comments.truncate(0);
+        assert_eq!(
+            errors,
+            vec!(LexicalError::UnrecognisedToken(
+                Loc::File(0, 1, 4),
+                "€".to_owned()
+            ))
+        );
 
-    let tokens = Lexer::new(r#"/// foo"#, 0, &mut comments, &mut errors).count();
+        let mut errors = Vec::new();
+        let _ = Lexer::new(r#"€"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    assert_eq!(tokens, 0);
-    assert_eq!(
-        comments,
-        vec![Comment::DocLine(Loc::File(0, 0, 7), "/// foo".to_owned())],
-    );
+        assert_eq!(
+            errors,
+            vec!(LexicalError::UnrecognisedToken(
+                Loc::File(0, 0, 3),
+                "€".to_owned()
+            ))
+        );
+
+        let tokens =
+            Lexer::new(r#"pragma foo bar"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Pragma, 6)),
+                Ok((7, Token::Identifier("foo"), 10)),
+                Ok((11, Token::StringLiteral(false, "bar"), 14)),
+            )
+        );
+
+        comments.truncate(0);
+
+        let tokens = Lexer::new(r#"/// foo"#, 0, &mut comments, &mut errors).count();
+
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec![Comment::DocLine(Loc::File(0, 0, 7), "/// foo".to_owned())],
+        );
 
-    comments.truncate(0);
+        comments.truncate(0);
 
-    let tokens = Lexer::new("/// jadajadadjada\n// bar", 0, &mut comments, &mut errors).count();
+        let tokens = Lexer::new("/// jadajadadjada\n// bar", 0, &mut comments, &mut errors).count();
 
-    assert_eq!(tokens, 0);
-    assert_eq!(
-        comments,
-        vec!(
-            Comment::DocLine(Loc::File(0, 0, 17), "/// jadajadadjada".to_owned()),
-            Comment::Line(Loc::File(0, 18, 24), "// bar".to_owned())
-        )
-    );
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(
+                Comment::DocLine(Loc::File(0, 0, 17), "/// jadajadadjada".to_owned()),
+                Comment::Line(Loc::File(0, 18, 24), "// bar".to_owned())
+            )
+        );
 
-    comments.truncate(0);
+        comments.truncate(0);
 
-    let tokens = Lexer::new("/**/", 0, &mut comments, &mut errors).count();
+        let tokens = Lexer::new("/**/", 0, &mut comments, &mut errors).count();
 
-    assert_eq!(tokens, 0);
-    assert_eq!(
-        comments,
-        vec!(Comment::Block(Loc::File(0, 0, 4), "/**/".to_owned()))
-    );
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(Comment::Block(Loc::File(0, 0, 4), "/**/".to_owned()))
+        );
 
-    comments.truncate(0);
+        comments.truncate(0);
 
-    let tokens = Lexer::new(r#"/** foo */"#, 0, &mut comments, &mut errors).count();
+        let tokens = Lexer::new(r#"/** foo */"#, 0, &mut comments, &mut errors).count();
 
-    assert_eq!(tokens, 0);
-    assert_eq!(
-        comments,
-        vec!(Comment::DocBlock(
-            Loc::File(0, 0, 10),
-            "/** foo */".to_owned()
-        ))
-    );
-
-    comments.truncate(0);
-
-    let tokens = Lexer::new(
-        "/** jadajadadjada */\n/* bar */",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .count();
-
-    assert_eq!(tokens, 0);
-    assert_eq!(
-        comments,
-        vec!(
-            Comment::DocBlock(Loc::File(0, 0, 20), "/** jadajadadjada */".to_owned()),
-            Comment::Block(Loc::File(0, 21, 30), "/* bar */".to_owned())
-        )
-    );
-
-    let tokens = Lexer::new("/************/", 0, &mut comments, &mut errors).next();
-    assert_eq!(tokens, None);
-
-    let mut errors = Vec::new();
-    let _ = Lexer::new("/**", 0, &mut comments, &mut errors).next();
-    assert_eq!(
-        errors,
-        vec!(LexicalError::EndOfFileInComment(Loc::File(0, 0, 3)))
-    );
-
-    let mut errors = Vec::new();
-    let tokens = Lexer::new("//////////////", 0, &mut comments, &mut errors).next();
-    assert_eq!(tokens, None);
-
-    // some unicode tests
-    let tokens = Lexer::new(
-        ">=\u{a0} . très\u{2028}αβγδεζηθικλμνξοπρστυφχψω\u{85}カラス",
-        0,
-        &mut comments,
-        &mut errors,
-    )
-    .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::MoreEqual, 2)),
-            Ok((5, Token::Member, 6)),
-            Ok((7, Token::Identifier("très"), 12)),
-            Ok((15, Token::Identifier("αβγδεζηθικλμνξοπρστυφχψω"), 63)),
-            Ok((65, Token::Identifier("カラス"), 74))
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(Comment::DocBlock(
+                Loc::File(0, 0, 10),
+                "/** foo */".to_owned()
+            ))
+        );
+
+        comments.truncate(0);
+
+        let tokens = Lexer::new(
+            "/** jadajadadjada */\n/* bar */",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new(r#"unicode"€""#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(tokens, vec!(Ok((0, Token::StringLiteral(true, "€"), 12)),));
-
-    let tokens = Lexer::new(r#"unicode "€""#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        .count();
 
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Identifier("unicode"), 7)),
-            Ok((8, Token::StringLiteral(false, "€"), 13)),
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(
+                Comment::DocBlock(Loc::File(0, 0, 20), "/** jadajadadjada */".to_owned()),
+                Comment::Block(Loc::File(0, 21, 30), "/* bar */".to_owned())
+            )
+        );
+
+        let tokens = Lexer::new("/************/", 0, &mut comments, &mut errors).next();
+        assert_eq!(tokens, None);
+
+        let mut errors = Vec::new();
+        let _ = Lexer::new("/**", 0, &mut comments, &mut errors).next();
+        assert_eq!(
+            errors,
+            vec!(LexicalError::EndOfFileInComment(Loc::File(0, 0, 3)))
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new("//////////////", 0, &mut comments, &mut errors).next();
+        assert_eq!(tokens, None);
+
+        // some unicode tests
+        let tokens = Lexer::new(
+            ">=\u{a0} . très\u{2028}αβγδεζηθικλμνξοπρστυφχψω\u{85}カラス",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    // scientific notation
-    let tokens = Lexer::new(r#" 1e0 "#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        .collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::MoreEqual, 2)),
+                Ok((5, Token::Member, 6)),
+                Ok((7, Token::Identifier("très"), 12)),
+                Ok((15, Token::Identifier("αβγδεζηθικλμνξοπρστυφχψω"), 63)),
+                Ok((65, Token::Identifier("カラス"), 74))
+            )
+        );
+
+        let tokens = Lexer::new(r#"unicode"€""#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(tokens, vec!(Ok((0, Token::StringLiteral(true, "€"), 12)),));
+
+        let tokens =
+            Lexer::new(r#"unicode "€""#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Identifier("unicode"), 7)),
+                Ok((8, Token::StringLiteral(false, "€"), 13)),
+            )
+        );
+
+        // scientific notation
+        let tokens = Lexer::new(r#" 1e0 "#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(tokens, vec!(Ok((1, Token::Number("1", "0"), 4)),));
+
+        let tokens = Lexer::new(r#" -9e0123"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((1, Token::Subtract, 2)),
+                Ok((2, Token::Number("9", "0123"), 8)),
+            )
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#" -9e"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(tokens, vec!(Ok((1, Token::Subtract, 2)),));
+        assert_eq!(
+            errors,
+            vec!(LexicalError::MissingExponent(Loc::File(0, 2, 4)))
+        );
 
-    assert_eq!(tokens, vec!(Ok((1, Token::Number("1", "0"), 4)),));
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#"9ea"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
 
-    let tokens = Lexer::new(r#" -9e0123"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        assert_eq!(tokens, vec!(Ok((2, Token::Identifier("a"), 3))));
+        assert_eq!(
+            errors,
+            vec!(LexicalError::MissingExponent(Loc::File(0, 0, 3)))
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#"42.a"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Number("42", ""), 2)),
+                Ok((2, Token::Member, 3)),
+                Ok((3, Token::Identifier("a"), 4))
+            )
+        );
+
+        let tokens = Lexer::new(r#"42..a"#, 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Number("42", ""), 2)),
+                Ok((2, Token::Member, 3)),
+                Ok((3, Token::Member, 4)),
+                Ok((4, Token::Identifier("a"), 5))
+            )
+        );
+
+        comments.truncate(0);
+
+        let tokens = Lexer::new("/// jadajadadjada\n// bar", 0, &mut comments, &mut errors).count();
+
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(
+                Comment::DocLine(Loc::File(0, 0, 17), "/// jadajadadjada".to_owned()),
+                Comment::Line(Loc::File(0, 18, 24), "// bar".to_owned())
+            )
+        );
 
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((1, Token::Subtract, 2)),
-            Ok((2, Token::Number("9", "0123"), 8)),
-        )
-    );
+        comments.truncate(0);
 
-    let mut errors = Vec::new();
-    let tokens = Lexer::new(r#" -9e"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        let tokens = Lexer::new("/**/", 0, &mut comments, &mut errors).count();
 
-    assert_eq!(tokens, vec!(Ok((1, Token::Subtract, 2)),));
-    assert_eq!(
-        errors,
-        vec!(LexicalError::MissingExponent(Loc::File(0, 2, 4)))
-    );
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(Comment::Block(Loc::File(0, 0, 4), "/**/".to_owned()))
+        );
 
-    let mut errors = Vec::new();
-    let tokens = Lexer::new(r#"9ea"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        comments.truncate(0);
 
-    assert_eq!(tokens, vec!(Ok((2, Token::Identifier("a"), 3))));
-    assert_eq!(
-        errors,
-        vec!(LexicalError::MissingExponent(Loc::File(0, 0, 3)))
-    );
+        let tokens = Lexer::new(r#"/** foo */"#, 0, &mut comments, &mut errors).count();
 
-    let mut errors = Vec::new();
-    let tokens = Lexer::new(r#"42.a"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Number("42", ""), 2)),
-            Ok((2, Token::Member, 3)),
-            Ok((3, Token::Identifier("a"), 4))
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(Comment::DocBlock(
+                Loc::File(0, 0, 10),
+                "/** foo */".to_owned()
+            ))
+        );
+
+        comments.truncate(0);
+
+        let tokens = Lexer::new(
+            "/** jadajadadjada */\n/* bar */",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let tokens = Lexer::new(r#"42..a"#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        .count();
 
-    assert_eq!(
-        tokens,
-        vec!(
-            Ok((0, Token::Number("42", ""), 2)),
-            Ok((2, Token::Member, 3)),
-            Ok((3, Token::Member, 4)),
-            Ok((4, Token::Identifier("a"), 5))
+        assert_eq!(tokens, 0);
+        assert_eq!(
+            comments,
+            vec!(
+                Comment::DocBlock(Loc::File(0, 0, 20), "/** jadajadadjada */".to_owned()),
+                Comment::Block(Loc::File(0, 21, 30), "/* bar */".to_owned())
+            )
+        );
+
+        let tokens = Lexer::new("/************/", 0, &mut comments, &mut errors).next();
+        assert_eq!(tokens, None);
+
+        let mut errors = Vec::new();
+        let _ = Lexer::new("/**", 0, &mut comments, &mut errors).next();
+        assert_eq!(
+            errors,
+            vec!(LexicalError::EndOfFileInComment(Loc::File(0, 0, 3)))
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new("//////////////", 0, &mut comments, &mut errors).next();
+        assert_eq!(tokens, None);
+
+        // some unicode tests
+        let tokens = Lexer::new(
+            ">=\u{a0} . très\u{2028}αβγδεζηθικλμνξοπρστυφχψω\u{85}カラス",
+            0,
+            &mut comments,
+            &mut errors,
         )
-    );
-
-    let mut errors = Vec::new();
-    let _ = Lexer::new(r#"hex"g""#, 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
-    assert_eq!(
-        errors,
-        vec!(LexicalError::InvalidCharacterInHexLiteral(
-            Loc::File(0, 4, 5),
-            'g'
-        ),)
-    );
-
-    let mut errors = Vec::new();
-    let tokens = Lexer::new(".9", 0, &mut comments, &mut errors)
         .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
 
-    assert_eq!(
-        tokens,
-        vec!(Ok((0, Token::RationalNumber("", "9", ""), 2)),)
-    );
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::MoreEqual, 2)),
+                Ok((5, Token::Member, 6)),
+                Ok((7, Token::Identifier("très"), 12)),
+                Ok((15, Token::Identifier("αβγδεζηθικλμνξοπρστυφχψω"), 63)),
+                Ok((65, Token::Identifier("カラス"), 74))
+            )
+        );
+
+        let tokens = Lexer::new(r#"unicode"€""#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(tokens, vec!(Ok((0, Token::StringLiteral(true, "€"), 12)),));
+
+        let tokens = Lexer::new(r#"unicode "€""#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Identifier("unicode"), 7)),
+                Ok((8, Token::StringLiteral(false, "€"), 13)),
+            )
+        );
+
+        // scientific notation
+        let tokens = Lexer::new(r#" 1e0 "#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(tokens, vec!(Ok((1, Token::Number("1", "0"), 4)),));
+
+        let tokens = Lexer::new(r#" -9e0123"#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((1, Token::Subtract, 2)),
+                Ok((2, Token::Number("9", "0123"), 8)),
+            )
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#" -9e"#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(tokens, vec!(Ok((1, Token::Subtract, 2)),));
+        assert_eq!(
+            errors,
+            vec!(LexicalError::MissingExponent(Loc::File(0, 2, 4)))
+        );
 
-    let mut errors = Vec::new();
-    let tokens = Lexer::new(".9e10", 0, &mut comments, &mut errors)
-        .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#"9ea"#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
 
-    assert_eq!(
-        tokens,
-        vec!(Ok((0, Token::RationalNumber("", "9", "10"), 5)),)
-    );
+        assert_eq!(tokens, vec!(Ok((2, Token::Identifier("a"), 3))));
+        assert_eq!(
+            errors,
+            vec!(LexicalError::MissingExponent(Loc::File(0, 0, 3)))
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(r#"42.a"#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Number("42", ""), 2)),
+                Ok((2, Token::Member, 3)),
+                Ok((3, Token::Identifier("a"), 4))
+            )
+        );
+
+        let tokens = Lexer::new(r#"42..a"#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(
+                Ok((0, Token::Number("42", ""), 2)),
+                Ok((2, Token::Member, 3)),
+                Ok((3, Token::Member, 4)),
+                Ok((4, Token::Identifier("a"), 5))
+            )
+        );
+
+        let mut errors = Vec::new();
+        let _ = Lexer::new(r#"hex"g""#, 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+        assert_eq!(
+            errors,
+            vec!(LexicalError::InvalidCharacterInHexLiteral(
+                Loc::File(0, 4, 5),
+                'g'
+            ),)
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(".9", 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::RationalNumber("", "9", ""), 2)),)
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(".9e10", 0, &mut comments, &mut errors)
+            .collect::<Vec<Result<(usize, Token, usize), LexicalError>>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::RationalNumber("", "9", "10"), 5)),)
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(".9", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::RationalNumber("", "9", ""), 2)),)
+        );
+
+        let mut errors = Vec::new();
+        let tokens = Lexer::new(".9e10", 0, &mut comments, &mut errors).collect::<Vec<_>>();
+
+        assert_eq!(
+            tokens,
+            vec!(Ok((0, Token::RationalNumber("", "9", "10"), 5)),)
+        );
+    }
 }

+ 16 - 16
solang-parser/src/lib.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #![doc = include_str!("../README.md")]
+#![warn(missing_debug_implementations, missing_docs)]
 
 use crate::lexer::LexicalError;
 use crate::lexer::Token;
@@ -11,17 +12,19 @@ use lalrpop_util::ParseError;
 
 pub mod diagnostics;
 pub mod doccomment;
+pub mod helpers;
 pub mod lexer;
 pub mod pt;
+
 #[cfg(test)]
-mod test;
+mod tests;
 
 #[allow(clippy::all)]
 mod solidity {
     include!(concat!(env!("OUT_DIR"), "/solidity.rs"));
 }
 
-/// Parse solidity file
+/// Parses a Solidity file.
 pub fn parse(
     src: &str,
     file_no: usize,
@@ -31,11 +34,10 @@ pub fn parse(
     let mut lexer_errors = Vec::new();
     let mut lex = lexer::Lexer::new(src, file_no, &mut comments, &mut lexer_errors);
 
-    let parser_errors = &mut Vec::new();
-    let diagnostics = &mut Vec::new();
-
-    let s = solidity::SourceUnitParser::new().parse(src, file_no, parser_errors, &mut lex);
+    let mut parser_errors = Vec::new();
+    let res = solidity::SourceUnitParser::new().parse(src, file_no, &mut parser_errors, &mut lex);
 
+    let mut diagnostics = Vec::with_capacity(lex.errors.len() + parser_errors.len());
     for lexical_error in lex.errors {
         diagnostics.push(Diagnostic::parser_error(
             lexical_error.loc(),
@@ -47,15 +49,13 @@ pub fn parse(
         diagnostics.push(parser_error_to_diagnostic(&e.error, file_no));
     }
 
-    if let Err(e) = s {
-        diagnostics.push(parser_error_to_diagnostic(&e, file_no));
-        return Err(diagnostics.to_vec());
-    }
-
-    if !diagnostics.is_empty() {
-        Err(diagnostics.to_vec())
-    } else {
-        Ok((s.unwrap(), comments))
+    match res {
+        Err(e) => {
+            diagnostics.push(parser_error_to_diagnostic(&e, file_no));
+            Err(diagnostics)
+        }
+        _ if !diagnostics.is_empty() => Err(diagnostics),
+        Ok(res) => Ok((res, comments)),
     }
 }
 
@@ -64,7 +64,7 @@ fn parser_error_to_diagnostic(
     error: &ParseError<usize, Token, LexicalError>,
     file_no: usize,
 ) -> Diagnostic {
-    match &error {
+    match error {
         ParseError::InvalidToken { location } => Diagnostic::parser_error(
             Loc::File(file_no, *location, *location),
             "invalid token".to_string(),

Fichier diff supprimé car celui-ci est trop grand
+ 701 - 224
solang-parser/src/pt.rs


+ 1 - 1
solang-parser/src/solidity.lalrpop

@@ -714,7 +714,7 @@ Using: Box<Using> = {
     }),
     <l:@L> "using" <false_token:!> <r:@R> ";" => {
         parser_errors.push(false_token);
-        let list = UsingList::Error();
+        let list = UsingList::Error;
         let global = None;
         Box::new(Using {
             loc: Loc::File(file_no, l, r),

+ 0 - 0
solang-parser/src/test.rs → solang-parser/src/tests.rs


+ 1 - 1
src/codegen/subexpression_elimination/available_variable.rs

@@ -41,7 +41,7 @@ impl AvailableVariable {
 }
 
 impl OptionalCodeLocation for AvailableVariable {
-    fn loc(&self) -> Option<Loc> {
+    fn loc_opt(&self) -> Option<Loc> {
         match self {
             AvailableVariable::Available(_, loc) => Some(*loc),
             _ => None,

+ 1 - 1
src/codegen/subexpression_elimination/common_subexpression_tracker.rs

@@ -83,7 +83,7 @@ impl<'a> CommonSubExpressionTracker<'a> {
         self.common_subexpressions.push(CommonSubexpression {
             in_cfg: node.available_variable.is_available(),
             var_no: node.available_variable.get_var_number(),
-            var_loc: node.available_variable.loc(),
+            var_loc: node.available_variable.loc_opt(),
             instantiated: false,
             var_type: exp.ty(),
             block: node.block,

+ 1 - 1
src/sema/ast.rs

@@ -1636,7 +1636,7 @@ pub enum DestructureField {
 }
 
 impl OptionalCodeLocation for DestructureField {
-    fn loc(&self) -> Option<pt::Loc> {
+    fn loc_opt(&self) -> Option<pt::Loc> {
         match self {
             DestructureField::None => None,
             DestructureField::Expression(e) => Some(e.loc()),

+ 2 - 2
src/sema/diagnostics.rs

@@ -253,8 +253,8 @@ impl Namespace {
                 sourceLocation: location,
                 ty: format!("{:?}", msg.ty),
                 component: "general".to_owned(),
-                severity: msg.level.to_string().to_owned(),
-                message: msg.message.to_owned(),
+                severity: msg.level.to_string(),
+                message: msg.message.clone(),
                 formattedMessage: buffer.into_string(),
             });
         }

+ 7 - 7
src/sema/functions.rs

@@ -148,9 +148,9 @@ pub fn contract_function(
             pt::FunctionAttribute::Visibility(v) => {
                 if let Some(e) = &visibility {
                     ns.diagnostics.push(Diagnostic::error_with_note(
-                        v.loc().unwrap(),
+                        v.loc_opt().unwrap(),
                         format!("function redeclared '{v}'"),
-                        e.loc().unwrap(),
+                        e.loc_opt().unwrap(),
                         format!("location of previous declaration of '{e}'"),
                     ));
                     success = false;
@@ -237,18 +237,18 @@ pub fn contract_function(
         Some(v) => {
             if func.ty == pt::FunctionTy::Modifier {
                 ns.diagnostics.push(Diagnostic::error(
-                    v.loc().unwrap(),
+                    v.loc_opt().unwrap(),
                     format!("'{v}': modifiers can not have visibility"),
                 ));
 
-                pt::Visibility::Internal(v.loc())
+                pt::Visibility::Internal(v.loc_opt())
             } else if func.ty == pt::FunctionTy::Constructor {
                 ns.diagnostics.push(Diagnostic::warning(
-                    v.loc().unwrap(),
+                    v.loc_opt().unwrap(),
                     format!("'{v}': visibility for constructors is ignored"),
                 ));
 
-                pt::Visibility::Public(v.loc())
+                pt::Visibility::Public(v.loc_opt())
             } else {
                 v
             }
@@ -705,7 +705,7 @@ pub fn function(
             }
             pt::FunctionAttribute::Visibility(v) => {
                 ns.diagnostics.push(Diagnostic::error(
-                    v.loc().unwrap(),
+                    v.loc_opt().unwrap(),
                     format!("'{v}': only functions in contracts can have a visibility specifier"),
                 ));
                 success = false;

+ 2 - 1
src/sema/mod.rs

@@ -10,7 +10,8 @@ use crate::sema::unused_variable::{check_unused_events, check_unused_namespace_v
 use num_bigint::BigInt;
 use solang_parser::{
     doccomment::{parse_doccomments, DocComment},
-    parse, pt,
+    parse,
+    pt::{self, CodeLocation},
 };
 use std::ffi::OsStr;
 

+ 3 - 3
src/sema/namespace.rs

@@ -892,7 +892,7 @@ impl Namespace {
                             }
                             pt::FunctionAttribute::Visibility(v) => {
                                 diagnostics.push(Diagnostic::error(
-                                    v.loc().unwrap(),
+                                    v.loc_opt().unwrap(),
                                     format!("function type cannot have visibility '{v}'"),
                                 ));
                                 success = false;
@@ -912,7 +912,7 @@ impl Namespace {
                         Some(pt::Visibility::External(_)) => true,
                         Some(v) => {
                             diagnostics.push(Diagnostic::error(
-                                v.loc().unwrap(),
+                                v.loc_opt().unwrap(),
                                 format!("function type cannot have visibility attribute '{v}'"),
                             ));
                             success = false;
@@ -963,7 +963,7 @@ impl Namespace {
                             }
                             pt::FunctionAttribute::Visibility(v) => {
                                 diagnostics.push(Diagnostic::error(
-                                    v.loc().unwrap(),
+                                    v.loc_opt().unwrap(),
                                     format!("function type cannot have visibility '{v}'"),
                                 ));
                                 success = false;

+ 1 - 1
src/sema/statements.rs

@@ -1508,7 +1508,7 @@ fn destructure_values(
     // Check that the values can be cast
     for (i, field) in fields.iter().enumerate() {
         if let Some(left_ty) = &left_tys[i] {
-            let loc = field.loc().unwrap();
+            let loc = field.loc_opt().unwrap();
             let _ = Expression::Variable {
                 loc,
                 ty: right_tys[i].clone(),

+ 1 - 1
src/sema/using.rs

@@ -259,7 +259,7 @@ pub(crate) fn using_decl(
             UsingList::Functions(res)
         }
 
-        pt::UsingList::Error() => unimplemented!(),
+        pt::UsingList::Error => unimplemented!(),
     };
 
     let mut file_no = Some(file_no);

+ 3 - 3
src/sema/variables.rs

@@ -203,7 +203,7 @@ pub fn variable_decl<'a>(
             }
             pt::VariableAttribute::Visibility(v) if contract_no.is_none() => {
                 ns.diagnostics.push(Diagnostic::error(
-                    v.loc().unwrap(),
+                    v.loc_opt().unwrap(),
                     format!("'{v}': global variable cannot have visibility specifier"),
                 ));
                 return None;
@@ -218,9 +218,9 @@ pub fn variable_decl<'a>(
             pt::VariableAttribute::Visibility(v) => {
                 if let Some(e) = &visibility {
                     ns.diagnostics.push(Diagnostic::error_with_note(
-                        v.loc().unwrap(),
+                        v.loc_opt().unwrap(),
                         format!("variable visibility redeclared '{v}'"),
-                        e.loc().unwrap(),
+                        e.loc_opt().unwrap(),
                         format!("location of previous declaration of '{e}'"),
                     ));
                     return None;

+ 4 - 1
src/sema/yul/block.rs

@@ -8,7 +8,10 @@ use crate::sema::yul::functions::{
     process_function_header, resolve_function_definition, FunctionsTable,
 };
 use crate::sema::yul::statements::resolve_yul_statement;
-use solang_parser::{diagnostics::Diagnostic, pt};
+use solang_parser::{
+    diagnostics::Diagnostic,
+    pt::{self, CodeLocation},
+};
 
 /// Resolve an yul block.
 /// Returns the resolved block and a boolean that tells us if the next statement is reachable.

+ 4 - 1
src/sema/yul/for_loop.rs

@@ -7,7 +7,10 @@ use crate::sema::yul::ast::{YulBlock, YulStatement};
 use crate::sema::yul::block::{process_statements, resolve_yul_block};
 use crate::sema::yul::functions::FunctionsTable;
 use crate::sema::yul::switch::resolve_condition;
-use solang_parser::{diagnostics::Diagnostic, pt};
+use solang_parser::{
+    diagnostics::Diagnostic,
+    pt::{self, CodeLocation},
+};
 
 /// Resolve a for-loop statement
 /// Returns the resolved block and a bool to indicate if the next statement is reachable.

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff