diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 54fa65ed5ba1..86d31833042e 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -19,6 +19,8 @@ pub struct FormatterConfig { pub quote_style: QuoteStyle, /// Style of underscores in number literals pub number_underscore: NumberUnderscore, + /// Style of underscores in hex literals + pub hex_underscore: HexUnderscore, /// Style of single line blocks in statements pub single_line_statement_blocks: SingleLineBlockStyle, /// Print space in state variable, function and modifier `override` attribute @@ -44,16 +46,70 @@ pub enum IntTypes { } /// Style of underscores in number literals -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum NumberUnderscore { + /// Use the underscores defined in the source code + #[default] + Preserve, /// Remove all underscores Remove, /// Add an underscore every thousand, if greater than 9999 /// e.g. 1000 -> 1000 and 10000 -> 10_000 Thousands, +} + +impl NumberUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, NumberUnderscore::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, NumberUnderscore::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_thousands(self) -> bool { + matches!(self, NumberUnderscore::Thousands) + } +} + +/// Style of underscores in hex literals +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HexUnderscore { /// Use the underscores defined in the source code Preserve, + /// Remove all underscores + #[default] + Remove, + /// Add underscore as separator between byte boundaries + Bytes, +} + +impl HexUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, HexUnderscore::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, HexUnderscore::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_bytes(self) -> bool { + matches!(self, HexUnderscore::Bytes) + } } /// Style of string quotes @@ -114,6 +170,7 @@ impl Default for FormatterConfig { multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst, quote_style: QuoteStyle::Double, number_underscore: NumberUnderscore::Preserve, + hex_underscore: HexUnderscore::Remove, single_line_statement_blocks: SingleLineBlockStyle::Preserve, override_spacing: false, wrap_comments: false, diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 4e2d3296802a..b62ed8136233 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -11,10 +11,10 @@ use crate::{ solang_ext::{pt::*, *}, string::{QuoteState, QuotedStringExt}, visit::{Visitable, Visitor}, - FormatterConfig, InlineConfig, IntTypes, NumberUnderscore, + FormatterConfig, InlineConfig, IntTypes, }; use alloy_primitives::Address; -use foundry_config::fmt::{MultilineFuncHeaderStyle, SingleLineBlockStyle}; +use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; use solang_parser::pt::ImportPath; use std::{fmt::Write, str::FromStr}; @@ -1304,7 +1304,7 @@ impl<'a, W: Write> Formatter<'a, W> { let config = self.config.number_underscore; // get source if we preserve underscores - let (value, fractional, exponent) = if matches!(config, NumberUnderscore::Preserve) { + let (value, fractional, exponent) = if config.is_preserve() { let source = &self.source[loc.start()..loc.end()]; // Strip unit let (source, _) = source.split_once(' ').unwrap_or((source, "")); @@ -1336,7 +1336,7 @@ impl<'a, W: Write> Formatter<'a, W> { exp = exp.trim().trim_start_matches('0'); let add_underscores = |string: &str, reversed: bool| -> String { - if !matches!(config, NumberUnderscore::Thousands) || string.len() < 5 { + if !config.is_thousands() || string.len() < 5 { return string.to_string() } if reversed { @@ -1377,6 +1377,32 @@ impl<'a, W: Write> Formatter<'a, W> { self.write_unit(unit) } + /// Write and hex literals according to the configuration. + fn write_hex_literal(&mut self, lit: &HexLiteral) -> Result<()> { + let HexLiteral { loc, hex } = lit; + match self.config.hex_underscore { + HexUnderscore::Remove => self.write_quoted_str(*loc, Some("hex"), hex), + HexUnderscore::Preserve => { + let quote = &self.source[loc.start()..loc.end()].trim_start_matches("hex"); + // source is always quoted so we remove the quotes first so we can adhere to the + // configured quoting style + let hex = "e[1..quote.len() - 1]; + self.write_quoted_str(*loc, Some("hex"), hex) + } + HexUnderscore::Bytes => { + // split all bytes + let hex = hex + .chars() + .chunks(2) + .into_iter() + .map(|chunk| chunk.collect::()) + .collect::>() + .join("_"); + self.write_quoted_str(*loc, Some("hex"), &hex) + } + } + } + /// Write built-in unit. fn write_unit(&mut self, unit: &mut Option) -> Result<()> { if let Some(unit) = unit { @@ -2061,8 +2087,8 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } } Expression::HexLiteral(vals) => { - for HexLiteral { loc, hex } in vals { - self.write_quoted_str(*loc, Some("hex"), hex)?; + for val in vals { + self.write_hex_literal(val)?; } } Expression::AddressLiteral(loc, val) => { diff --git a/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol b/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol new file mode 100644 index 000000000000..b3be2a8657c1 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "bytes" +contract HexLiteral { + function test() external { + hex"01_23_00_00"; + hex"01_23_00_00"; + hex"01_23_00_00"; + hex""; + hex"60_01_60_02_53"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/fmt.sol b/crates/fmt/testdata/HexUnderscore/fmt.sol new file mode 100644 index 000000000000..0c8710a92475 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/fmt.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/original.sol b/crates/fmt/testdata/HexUnderscore/original.sol new file mode 100644 index 000000000000..5c2918747548 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/original.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} \ No newline at end of file diff --git a/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol b/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol new file mode 100644 index 000000000000..0f5db52e3c3f --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "preserve" +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/remove.fmt.sol b/crates/fmt/testdata/HexUnderscore/remove.fmt.sol new file mode 100644 index 000000000000..39aae1465cf7 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/remove.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "remove" +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol new file mode 100644 index 000000000000..d87dc99d9653 --- /dev/null +++ b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol @@ -0,0 +1,26 @@ +// config: number_underscore = "preserve" +contract NumberLiteral { + function test() external { + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + 0.1; + 1.3; + 2.5e1; + 1.23454; + 1.2e34_5_678; + 134411.2e34_5_678; + 13431.134112e34_135_678; + 13431.0134112; + 13431.134112e-139_3141340; + 134411.2e34_5_6780; + 13431.134112e34_135_6780; + 0.134112; + 1.0; + 13431.134112e-139_3141340; + 123e456; + 1_000; + } +} diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 7aaed58e5fb8..5ef7154cd047 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -193,6 +193,7 @@ test_directories! { IntTypes, InlineDisable, NumberLiteralUnderscore, + HexUnderscore, FunctionCall, TrailingComma, PragmaDirective,