Skip to content

Commit

Permalink
Support for configurable blocks in the formatter (FuelLabs#4105)
Browse files Browse the repository at this point in the history
## Description
Closes FuelLabs#3679

This is based on how `storage` blocks are handled.. Most of the code is
copied from `item_storage`. We should probably consider making as much
as that code common between the two because `storage` and `configurable`
blocks look pretty much the same.

Making this change now so that the formatter does not panic when we add
an example using `configurable` under `sway/examples`.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

Co-authored-by: Kaya Gökalp <[email protected]>
  • Loading branch information
mohammadfawaz and kayagokalp authored Feb 17, 2023
1 parent ceb384b commit ebe7afe
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 23 deletions.
188 changes: 188 additions & 0 deletions swayfmt/src/items/item_configurable/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use crate::{
config::{items::ItemBraceStyle, user_def::FieldAlignment},
formatter::{
shape::{ExprKind, LineStyle},
*,
},
utils::{
map::byte_span::{ByteSpan, LeafSpans},
CurlyBrace,
},
};
use std::fmt::Write;
use sway_ast::{keywords::Token, token::Delimiter, ConfigurableField, ItemConfigurable};
use sway_types::Spanned;

#[cfg(test)]
mod tests;

impl Format for ItemConfigurable {
fn format(
&self,
formatted_code: &mut FormattedCode,
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
formatter.with_shape(
formatter
.shape
.with_code_line_from(LineStyle::Multiline, ExprKind::default()),
|formatter| -> Result<(), FormatterError> {
// Add configurable token
write!(
formatted_code,
"{}",
self.configurable_token.span().as_str()
)?;
let fields = self.fields.get();

// Handle openning brace
Self::open_curly_brace(formatted_code, formatter)?;

// Determine alignment tactic
match formatter.config.structures.field_alignment {
FieldAlignment::AlignFields(configurable_field_align_threshold) => {
writeln!(formatted_code)?;
let value_pairs = &fields
.value_separator_pairs
.iter()
// TODO: Handle annotations instead of stripping them
.map(|(configurable_field, comma_token)| {
(&configurable_field.value, comma_token)
})
.collect::<Vec<_>>();
// In first iteration we are going to be collecting the lengths of the
// struct fields.
let field_length: Vec<usize> = value_pairs
.iter()
.map(|(configurable_field, _)| configurable_field.name.as_str().len())
.collect();

// Find the maximum length in the `field_length` vector that is still
// smaller than `configurable_field_align_threshold`.
// `max_valid_field_length`: the length of the field that we are taking as
// a reference to align.
let mut max_valid_field_length = 0;
field_length.iter().for_each(|length| {
if *length > max_valid_field_length
&& *length < configurable_field_align_threshold
{
max_valid_field_length = *length;
}
});

let value_pairs_iter = value_pairs.iter().enumerate();
for (field_index, (configurable_field, comma_token)) in
value_pairs_iter.clone()
{
write!(
formatted_code,
"{}",
&formatter.shape.indent.to_string(&formatter.config)?
)?;

// Add name
configurable_field.name.format(formatted_code, formatter)?;

// `current_field_length`: the length of the current field that we are
// trying to format.
let current_field_length = field_length[field_index];
if current_field_length < max_valid_field_length {
// We need to add alignment between `:` and `ty`
let mut required_alignment =
max_valid_field_length - current_field_length;
while required_alignment != 0 {
write!(formatted_code, " ")?;
required_alignment -= 1;
}
}
// Add `:`, `ty` & `CommaToken`
write!(
formatted_code,
" {} ",
configurable_field.colon_token.ident().as_str(),
)?;
configurable_field.ty.format(formatted_code, formatter)?;
write!(
formatted_code,
" {} ",
configurable_field.eq_token.ident().as_str()
)?;
configurable_field
.initializer
.format(formatted_code, formatter)?;
writeln!(formatted_code, "{}", comma_token.ident().as_str())?;
}
if let Some(final_value) = &fields.final_value_opt {
final_value.format(formatted_code, formatter)?;
}
}
FieldAlignment::Off => fields.format(formatted_code, formatter)?,
}
// Handle closing brace
Self::close_curly_brace(formatted_code, formatter)?;

Ok(())
},
)?;

Ok(())
}
}

impl CurlyBrace for ItemConfigurable {
fn open_curly_brace(
line: &mut String,
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
let brace_style = formatter.config.items.item_brace_style;
formatter.shape.block_indent(&formatter.config);
let open_brace = Delimiter::Brace.as_open_char();
match brace_style {
ItemBraceStyle::AlwaysNextLine => {
// Add opening brace to the next line.
write!(line, "\n{open_brace}")?;
}
_ => {
// Add opening brace to the same line
write!(line, " {open_brace}")?;
}
}

Ok(())
}
fn close_curly_brace(
line: &mut String,
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
// shrink_left would return error if the current indentation level is becoming < 0, in that
// case we should use the Shape::default() which has 0 indentation level.
formatter.shape.block_unindent(&formatter.config);
write!(
line,
"{}{}",
formatter.shape.indent.to_string(&formatter.config)?,
Delimiter::Brace.as_close_char()
)?;

Ok(())
}
}

impl LeafSpans for ItemConfigurable {
fn leaf_spans(&self) -> Vec<ByteSpan> {
let mut collected_spans = vec![ByteSpan::from(self.configurable_token.span())];
collected_spans.append(&mut self.fields.leaf_spans());
collected_spans
}
}

impl LeafSpans for ConfigurableField {
fn leaf_spans(&self) -> Vec<ByteSpan> {
let mut collected_spans = vec![ByteSpan::from(self.name.span())];
collected_spans.push(ByteSpan::from(self.colon_token.span()));
collected_spans.append(&mut self.ty.leaf_spans());
collected_spans.push(ByteSpan::from(self.eq_token.span()));
collected_spans.append(&mut self.initializer.leaf_spans());
collected_spans
}
}
93 changes: 93 additions & 0 deletions swayfmt/src/items/item_configurable/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use forc_tracing::{println_green, println_red};
use paste::paste;
use prettydiff::{basic::DiffOp, diff_lines};

macro_rules! fmt_test {
($scope:ident $desired_output:expr, $($name:ident $y:expr),+) => {
fmt_test_inner!($scope $desired_output,
$($name $y)+
,
remove_trailing_whitespace format!("{} \n\n\t ", $desired_output).as_str(),
remove_beginning_whitespace format!(" \n\t{}", $desired_output).as_str(),
identity $desired_output, /* test return is valid */
remove_beginning_and_trailing_whitespace format!(" \n\t {} \n\t ", $desired_output).as_str()
);
};
}

macro_rules! fmt_test_inner {
($scope:ident $desired_output:expr, $($name:ident $y:expr),+) => {
$(
paste! {
#[test]
fn [<$scope _ $name>] () {
let formatted_code = crate::parse::parse_format::<sway_ast::ItemConfigurable>($y);
let changeset = diff_lines(&formatted_code, $desired_output);
let diff = changeset.diff();
let count_of_updates = diff.len();
if count_of_updates != 0 {
println!("FAILED: {count_of_updates} diff items.");
}
for diff in diff {
match diff {
DiffOp::Equal(old) => {
for o in old {
println!("{}", o)
}
}
DiffOp::Insert(new) => {
for n in new {
println_green(&format!("+{}", n));
}
}
DiffOp::Remove(old) => {
for o in old {
println_red(&format!("-{}", o));
}
}
DiffOp::Replace(old, new) => {
for o in old {
println_red(&format!("-{}", o));
}
for n in new {
println_green(&format!("+{}", n));
}
}
}
}
assert_eq!(&formatted_code, $desired_output)
}
}
)+
}
}

fmt_test!( configurables
"configurable {
C0: bool = true,
C1: u64 = 42,
C2: b256 = 0x1111111111111111111111111111111111111111111111111111111111111111,
C3: MyStruct = MyStruct { x: 42, y: true },
C4: MyEnum = MyEnum::A(42),
C5: MyEnum = MyEnum::B(true),
C6: str[4] = \"fuel\",
C7: [u64; 4] = [1, 2, 3, 4],
C8: u64 = 0,
}",
wrong_new_lines
"configurable {
C0: bool = true, C1: u64 = 42,
C2: b256 =
0x1111111111111111111111111111111111111111111111111111111111111111,
C3: MyStruct =
MyStruct { x: 42,
y: true },
C4: MyEnum
= MyEnum::A(42),
C5: MyEnum = MyEnum::B(true),
C6: str[4] = \"fuel\",
C7: [u64; 4] = [1, 2,
3, 4], C8: u64 = 0,
}"
);
11 changes: 7 additions & 4 deletions swayfmt/src/items/item_storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,16 @@ impl Format for ItemStorage {
// TODO: Handle annotations instead of stripping them
.map(|(storage_field, comma_token)| (&storage_field.value, comma_token))
.collect::<Vec<_>>();
// In first iteration we are going to be collecting the lengths of the struct fields.
// In first iteration we are going to be collecting the lengths of the
// struct fields.
let field_length: Vec<usize> = value_pairs
.iter()
.map(|(storage_field, _)| storage_field.name.as_str().len())
.collect();

// Find the maximum length in the `field_length` vector that is still smaller than `storage_field_align_threshold`.
// `max_valid_field_length`: the length of the field that we are taking as a reference to align.
// Find the maximum length in the `field_length` vector that is still
// smaller than `storage_field_align_threshold`. `max_valid_field_length`:
// the length of the field that we are taking as a reference to align.
let mut max_valid_field_length = 0;
field_length.iter().for_each(|length| {
if *length > max_valid_field_length
Expand All @@ -73,7 +75,8 @@ impl Format for ItemStorage {
// Add name
storage_field.name.format(formatted_code, formatter)?;

// `current_field_length`: the length of the current field that we are trying to format.
// `current_field_length`: the length of the current field that we are
// trying to format.
let current_field_length = field_length[field_index];
if current_field_length < max_valid_field_length {
// We need to add alignment between `:` and `ty`
Expand Down
1 change: 1 addition & 0 deletions swayfmt/src/items/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod item_abi;
mod item_configurable;
mod item_const;
mod item_enum;
mod item_fn;
Expand Down
4 changes: 2 additions & 2 deletions swayfmt/src/module/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Format for ItemKind {
Abi(item_abi) => item_abi.format(formatted_code, formatter),
Const(item_const) => item_const.format(formatted_code, formatter),
Storage(item_storage) => item_storage.format(formatted_code, formatter),
Configurable(_item_configurable) => todo!(),
Configurable(item_configurable) => item_configurable.format(formatted_code, formatter),
}
}
}
Expand All @@ -39,7 +39,7 @@ impl LeafSpans for ItemKind {
Trait(item_trait) => item_trait.leaf_spans(),
Impl(item_impl) => item_impl.leaf_spans(),
Use(item_use) => item_use.leaf_spans(),
Configurable(_item_configurable) => todo!(),
Configurable(item_configurable) => item_configurable.leaf_spans(),
}
}
}
Expand Down
31 changes: 30 additions & 1 deletion swayfmt/src/utils/language/punctuated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use crate::{
};
use std::fmt::Write;
use sway_ast::{
keywords::CommaToken, punctuated::Punctuated, token::PunctKind, StorageField, TypeField,
keywords::CommaToken, punctuated::Punctuated, token::PunctKind, ConfigurableField,
StorageField, TypeField,
};
use sway_types::{Ident, Spanned};

Expand Down Expand Up @@ -153,6 +154,34 @@ impl Format for TypeField {
}
}

impl Format for ConfigurableField {
fn format(
&self,
formatted_code: &mut FormattedCode,
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
formatter.with_shape(
formatter.shape.with_default_code_line(),
|formatter| -> Result<(), FormatterError> {
write!(
formatted_code,
"{}{} ",
self.name.span().as_str(),
self.colon_token.span().as_str(),
)?;
self.ty.format(formatted_code, formatter)?;
write!(formatted_code, " {} ", self.eq_token.span().as_str())?;

Ok(())
},
)?;

self.initializer.format(formatted_code, formatter)?;

Ok(())
}
}

impl Format for StorageField {
fn format(
&self,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
entry = "main.sw"
implicit-std = false
license = "Apache-2.0"
name = "configurables_are_immutable"
entry = "main.sw"
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
script;

configurable {
C1:u64 = 5,
C1: u64 = 5,
}

fn main() -> u64 {
Expand Down
Loading

0 comments on commit ebe7afe

Please sign in to comment.