Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: enhance code formatting for If expressions #3246

Merged
merged 10 commits into from Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tooling/nargo_fmt/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@
remove_nested_parens: bool, true, "Remove nested parens";
short_array_element_width_threshold: usize, 10, "Width threshold for an array element to be considered short";
array_width: usize, 100, "Maximum width of an array literal before falling back to vertical formatting";
single_line_if_else_max_width: usize, 100, "Maximum line length for single line if-else expressions";
}

impl Config {
pub fn read(path: &Path) -> Result<Self, ConfigError> {
let mut config = Self::default();
let config_path = path.join("noirfmt.toml");

Check warning on line 56 in tooling/nargo_fmt/src/config.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (noirfmt)

let raw_toml = match std::fs::read_to_string(&config_path) {
Ok(t) => t,
Expand Down
4 changes: 2 additions & 2 deletions tooling/nargo_fmt/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
let starts_with_single_line_comment = leading_trimmed.starts_with("//");

if ends_with_block_comment {
let comment_end = leading_trimmed.rfind(|c| c == '/').unwrap();

Check warning on line 100 in tooling/nargo_fmt/src/utils.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (rfind)

if leading[comment_end..].contains('\n') {
different_line = true;
Expand Down Expand Up @@ -218,7 +218,7 @@
}

fn format(self, visitor: &FmtVisitor) -> String {
visitor.format_expr(self)
visitor.format_subexpr(self)

Check warning on line 221 in tooling/nargo_fmt/src/utils.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
}
}

Expand All @@ -232,7 +232,7 @@
let (name, expr) = self;

let name = name.0.contents;
let expr = visitor.format_expr(expr);
let expr = visitor.format_subexpr(expr);

Check warning on line 235 in tooling/nargo_fmt/src/utils.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)

if name == expr {
name
Expand Down
8 changes: 7 additions & 1 deletion tooling/nargo_fmt/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
let newline_upper_bound = 2;
let newline_lower_bound = 1;

let mut newline_count = bytecount::count(slice.as_bytes(), b'\n');

Check warning on line 134 in tooling/nargo_fmt/src/visitor.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (bytecount)
let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count();

if newline_count + offset > newline_upper_bound {
Expand Down Expand Up @@ -164,7 +164,7 @@
}
}

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
struct Indent {
block_indent: usize,
}
Expand Down Expand Up @@ -197,3 +197,9 @@
width: usize,
indent: Indent,
}

#[derive(PartialEq, Eq, Debug)]
pub(crate) enum ExpressionType {
Statement,
SubExpression,
}
114 changes: 95 additions & 19 deletions tooling/nargo_fmt/src/visitor/expr.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
use noirc_frontend::{
hir::resolution::errors::Span, token::Token, ArrayLiteral, BlockExpression,
ConstructorExpression, Expression, ExpressionKind, Literal, Statement, UnaryOp,
ConstructorExpression, Expression, ExpressionKind, IfExpression, Literal, Statement,
StatementKind, UnaryOp,
};

use super::{FmtVisitor, Shape};
use super::{ExpressionType, FmtVisitor, Indent, Shape};
use crate::{
utils::{self, Expr, FindToken, Item},
Config,
};

impl FmtVisitor<'_> {
pub(crate) fn visit_expr(&mut self, expr: Expression) {
pub(crate) fn visit_expr(&mut self, expr: Expression, expr_type: ExpressionType) {
let span = expr.span;

let rewrite = self.format_expr(expr);
let rewrite = self.format_expr(expr, expr_type);
let rewrite = utils::recover_comment_removed(self.slice(span), rewrite);
self.push_rewrite(rewrite, span);

self.last_position = span.end();
}

pub(crate) fn format_expr(&self, Expression { kind, mut span }: Expression) -> String {
pub(crate) fn format_subexpr(&self, expression: Expression) -> String {

Check warning on line 24 in tooling/nargo_fmt/src/visitor/expr.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
self.format_expr(expression, ExpressionType::SubExpression)
}

pub(crate) fn format_expr(
&self,
Expression { kind, mut span }: Expression,
expr_type: ExpressionType,
) -> String {
match kind {
ExpressionKind::Block(block) => {
let mut visitor = self.fork();
Expand All @@ -41,24 +50,24 @@
}
};

format!("{op}{}", self.format_expr(prefix.rhs))
format!("{op}{}", self.format_subexpr(prefix.rhs))

Check warning on line 53 in tooling/nargo_fmt/src/visitor/expr.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
}
ExpressionKind::Cast(cast) => {
format!("{} as {}", self.format_expr(cast.lhs), cast.r#type)
format!("{} as {}", self.format_subexpr(cast.lhs), cast.r#type)

Check warning on line 56 in tooling/nargo_fmt/src/visitor/expr.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
}
ExpressionKind::Infix(infix) => {
format!(
"{} {} {}",
self.format_expr(infix.lhs),
self.format_subexpr(infix.lhs),

Check warning on line 61 in tooling/nargo_fmt/src/visitor/expr.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
infix.operator.contents.as_string(),
self.format_expr(infix.rhs)
self.format_subexpr(infix.rhs)

Check warning on line 63 in tooling/nargo_fmt/src/visitor/expr.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (subexpr)
)
}
ExpressionKind::Call(call_expr) => {
let args_span =
self.span_before(call_expr.func.span.end()..span.end(), Token::LeftParen);

let callee = self.format_expr(*call_expr.func);
let callee = self.format_subexpr(*call_expr.func);
let args = format_parens(self.fork(), false, call_expr.arguments, args_span);

format!("{callee}{args}")
Expand All @@ -69,21 +78,21 @@
Token::LeftParen,
);

let object = self.format_expr(method_call_expr.object);
let object = self.format_subexpr(method_call_expr.object);
let method = method_call_expr.method_name.to_string();
let args = format_parens(self.fork(), false, method_call_expr.arguments, args_span);

format!("{object}.{method}{args}")
}
ExpressionKind::MemberAccess(member_access_expr) => {
let lhs_str = self.format_expr(member_access_expr.lhs);
let lhs_str = self.format_subexpr(member_access_expr.lhs);
format!("{}.{}", lhs_str, member_access_expr.rhs)
}
ExpressionKind::Index(index_expr) => {
let index_span = self
.span_before(index_expr.collection.span.end()..span.end(), Token::LeftBracket);

let collection = self.format_expr(index_expr.collection);
let collection = self.format_subexpr(index_expr.collection);
let index = format_brackets(self.fork(), false, vec![index_expr.index], index_span);

format!("{collection}{index}")
Expand All @@ -96,8 +105,8 @@
self.slice(span).to_string()
}
Literal::Array(ArrayLiteral::Repeated { repeated_element, length }) => {
let repeated = self.format_expr(*repeated_element);
let length = self.format_expr(*length);
let repeated = self.format_subexpr(*repeated_element);
let length = self.format_subexpr(*length);

format!("[{repeated}; {length}]")
}
Expand Down Expand Up @@ -131,7 +140,7 @@
}

if !leading.contains("//") && !trailing.contains("//") {
let sub_expr = self.format_expr(*sub_expr);
let sub_expr = self.format_subexpr(*sub_expr);
format!("({leading}{sub_expr}{trailing})")
} else {
let mut visitor = self.fork();
Expand All @@ -140,7 +149,7 @@
visitor.indent.block_indent(self.config);
let nested_indent = visitor.indent.to_string_with_newline();

let sub_expr = visitor.format_expr(*sub_expr);
let sub_expr = visitor.format_subexpr(*sub_expr);

let mut result = String::new();
result.push('(');
Expand Down Expand Up @@ -171,11 +180,66 @@

self.format_struct_lit(type_name, fields_span, *constructor)
}
// TODO:
_expr => self.slice(span).to_string(),
ExpressionKind::If(if_expr) => {
let allow_single_line = expr_type == ExpressionType::SubExpression;

if allow_single_line {
let mut visitor = self.fork();
visitor.indent = Indent::default();
if let Some(line) = visitor.format_if_single_line(*if_expr.clone()) {
return line;
}
}

self.format_if(*if_expr)
}
_ => self.slice(span).to_string(),
}
}

fn format_if(&self, if_expr: IfExpression) -> String {
let condition_str = self.format_subexpr(if_expr.condition);
let consequence_str = self.format_subexpr(if_expr.consequence);

let mut result = format!("if {condition_str} {consequence_str}");

if let Some(alternative) = if_expr.alternative {
let alternative = if let Some(ExpressionKind::If(if_expr)) =
extract_simple_expr(alternative.clone()).map(|expr| expr.kind)
{
self.format_if(*if_expr)
} else {
self.format_expr(alternative, ExpressionType::Statement)
};

result.push_str(" else ");
result.push_str(&alternative);
};

result
}

fn format_if_single_line(&self, if_expr: IfExpression) -> Option<String> {
let condition_str = self.format_subexpr(if_expr.condition);
let consequence_str = self.format_subexpr(extract_simple_expr(if_expr.consequence)?);

let if_str = if let Some(alternative) = if_expr.alternative {
let alternative_str = if let Some(ExpressionKind::If(_)) =
extract_simple_expr(alternative.clone()).map(|expr| expr.kind)
{
return None;
} else {
self.format_expr(extract_simple_expr(alternative)?, ExpressionType::Statement)
};

format!("if {} {{ {} }} else {{ {} }}", condition_str, consequence_str, alternative_str)
} else {
format!("if {{{}}} {{{}}}", condition_str, consequence_str)
};

(if_str.len() <= self.config.single_line_if_else_max_width).then_some(if_str)
}

fn format_struct_lit(
&self,
type_name: &str,
Expand Down Expand Up @@ -515,3 +579,15 @@
fn no_long_exprs(exprs: &[Expr], max_width: usize) -> bool {
exprs.iter().all(|expr| expr.value.len() <= max_width)
}

fn extract_simple_expr(expr: Expression) -> Option<Expression> {
if let ExpressionKind::Block(mut block) = expr.kind {
if block.len() == 1 {
if let StatementKind::Expression(expr) = block.pop().unwrap() {
return expr.into();
}
}
}

None
}
20 changes: 16 additions & 4 deletions tooling/nargo_fmt/src/visitor/stmt.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
use std::iter::zip;

use noirc_frontend::{Statement, StatementKind};

use super::ExpressionType;

impl super::FmtVisitor<'_> {
pub(crate) fn visit_stmts(&mut self, stmts: Vec<Statement>) {
for Statement { kind, span } in stmts {
let len = stmts.len();

for (Statement { kind, span }, index) in zip(stmts, 1..) {
let is_last = index == len;

match kind {
StatementKind::Expression(expr) => self.visit_expr(expr),
StatementKind::Expression(expr) => self.visit_expr(
expr,
if is_last { ExpressionType::SubExpression } else { ExpressionType::Statement },
),
StatementKind::Semi(expr) => {
self.visit_expr(expr);
self.visit_expr(expr, ExpressionType::Statement);
self.push_str(";");
}
StatementKind::Let(let_stmt) => {
let let_str =
self.slice(span.start()..let_stmt.expression.span.start()).trim_end();
let expr_str = self.format_expr(let_stmt.expression);
let expr_str =
self.format_expr(let_stmt.expression, ExpressionType::SubExpression);

self.push_rewrite(format!("{let_str} {expr_str};"), span);
}
Expand Down
26 changes: 26 additions & 0 deletions tooling/nargo_fmt/tests/expected/expr.nr
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,29 @@ fn parenthesized() {
fn constructor() {
Point { x: 5, y: 10 };
}

fn if_expr() {
if true {
println("Hello :D");
}
}

fn return_if_expr() {
if true { 42 } else { 40 + 2 }
}

fn return_if_expr() {
if true {
42
};

if true { 42 } else { 40 + 2 }
}

fn if_if() {
if cond {
some();
} else {
none();
}.bar().baz();
}
40 changes: 40 additions & 0 deletions tooling/nargo_fmt/tests/expected/if.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
fn main() {
if false {
();
();
}

if false // lone if comment
{
();
();
}

let a = if 0 > 1 { 0 } else { 0 };

if true {
();
} else if false {
();
();
} else {
();
();
();
}

if true // else-if-chain if comment
{
();
}
else if false // else-if-chain else-if comment
{
();
();
} else // else-if-chain else comment
{
();
();
();
}
}
8 changes: 7 additions & 1 deletion tooling/nargo_fmt/tests/expected/nested_if_else.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
fn nested_if_else() {
if false { 1 } else if false { 2 } else { 3 }
if false {
1
} else if false {
2
} else {
3
}
}
Loading