From a3f8fd71df0e09b6cd161a7e7df78c67bb47d329 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 15:00:25 +0530 Subject: [PATCH 1/2] fix: format expression parsing edge-cases handle positional arg with formatting handle indexed positional args --- .../src/handlers/move_format_string_arg.rs | 6 +- .../src/syntax_helpers/format_string_exprs.rs | 62 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/crates/ide-assists/src/handlers/move_format_string_arg.rs b/crates/ide-assists/src/handlers/move_format_string_arg.rs index aa710d2ce6513..11db6ae7f7b81 100644 --- a/crates/ide-assists/src/handlers/move_format_string_arg.rs +++ b/crates/ide-assists/src/handlers/move_format_string_arg.rs @@ -92,7 +92,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) NodeOrToken::Node(n) => { format_to!(current_arg, "{n}"); }, - NodeOrToken::Token(t) if t.kind() == COMMA=> { + NodeOrToken::Token(t) if t.kind() == COMMA => { existing_args.push(current_arg.trim().into()); current_arg.clear(); }, @@ -238,14 +238,14 @@ fn main() { &add_macro_decl( r#" fn main() { - print!("{} {x + 1:b} {Struct(1, 2)}$0", 1); + print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); } "#, ), &add_macro_decl( r#" fn main() { - print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); + print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, ), diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs index ac6c6e8feeea0..c3b7bb27d88bb 100644 --- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs +++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs @@ -103,7 +103,12 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { output.push(chr); extracted_expressions.push(Arg::Placeholder); state = State::NotArg; - } + }, + (State::MaybeArg, ':') => { + output.push(chr); + extracted_expressions.push(Arg::Placeholder); + state = State::FormatOpts; + }, (State::MaybeArg, _) => { if matches!(chr, '\\' | '$') { current_expr.push('\\'); @@ -117,49 +122,40 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { } else { state = State::Expr; } - } - (State::Ident | State::Expr, '}') => { - if inexpr_open_count == 0 { - output.push(chr); - - if matches!(state, State::Expr) { - extracted_expressions.push(Arg::Expr(current_expr.trim().into())); - } else { - extracted_expressions.push(Arg::Ident(current_expr.trim().into())); - } - - current_expr = String::new(); - state = State::NotArg; - } else { - // We're closing one brace met before inside of the expression. - current_expr.push(chr); - inexpr_open_count -= 1; - } - } + }, (State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => { // path separator state = State::Expr; current_expr.push_str("::"); chars.next(); - } - (State::Ident | State::Expr, ':') => { + }, + (State::Ident | State::Expr, ':' | '}') => { if inexpr_open_count == 0 { - // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}" - output.push(chr); + let trimmed = current_expr.trim(); - if matches!(state, State::Expr) { - extracted_expressions.push(Arg::Expr(current_expr.trim().into())); + // if the expression consists of a single number, like "0" or "12", it can refer to + // format args in the order they are specified. + // see: https://doc.rust-lang.org/std/fmt/#positional-parameters + if trimmed.chars().fold(true, |only_num, c| c.is_ascii_digit() && only_num) { + output.push_str(trimmed); + } else if matches!(state, State::Expr) { + extracted_expressions.push(Arg::Expr(trimmed.into())); } else { - extracted_expressions.push(Arg::Ident(current_expr.trim().into())); + extracted_expressions.push(Arg::Ident(trimmed.into())); } - current_expr = String::new(); - state = State::FormatOpts; - } else { + output.push(chr); + current_expr.clear(); + state = if chr == ':' {State::FormatOpts} else if chr == '}' {State::NotArg} else {unreachable!()}; + } else if chr == '}' { + // We're closing one brace met before inside of the expression. + current_expr.push(chr); + inexpr_open_count -= 1; + } else if chr == ':' { // We're inside of braced expression, assume that it's a struct field name/value delimiter. current_expr.push(chr); } - } + }, (State::Ident | State::Expr, '{') => { state = State::Expr; current_expr.push(chr); @@ -219,6 +215,10 @@ mod tests { ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]), ("{expr:?}", expect![["{:?}; expr"]]), ("{expr:1$}", expect![[r"{:1\$}; expr"]]), + ("{:1$}", expect![[r"{:1\$}; $1"]]), + ("{:>padding$}", expect![[r"{:>padding\$}; $1"]]), + ("{}, {}, {0}", expect![[r"{}, {}, {0}; $1, $2"]]), + ("{}, {}, {0:b}", expect![[r"{}, {}, {0:b}; $1, $2"]]), ("{$0}", expect![[r"{}; \$0"]]), ("{malformed", expect![["-"]]), ("malformed}", expect![["-"]]), From 6d4b2b4b17841cdb29e99bf7e7e71b57dbaa2dc1 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 15:08:32 +0530 Subject: [PATCH 2/2] run cargo fmt --- .../src/syntax_helpers/format_string_exprs.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs index c3b7bb27d88bb..313346ee13153 100644 --- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs +++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs @@ -103,12 +103,12 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { output.push(chr); extracted_expressions.push(Arg::Placeholder); state = State::NotArg; - }, + } (State::MaybeArg, ':') => { output.push(chr); extracted_expressions.push(Arg::Placeholder); state = State::FormatOpts; - }, + } (State::MaybeArg, _) => { if matches!(chr, '\\' | '$') { current_expr.push('\\'); @@ -122,13 +122,13 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { } else { state = State::Expr; } - }, + } (State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => { // path separator state = State::Expr; current_expr.push_str("::"); chars.next(); - }, + } (State::Ident | State::Expr, ':' | '}') => { if inexpr_open_count == 0 { let trimmed = current_expr.trim(); @@ -146,7 +146,13 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { output.push(chr); current_expr.clear(); - state = if chr == ':' {State::FormatOpts} else if chr == '}' {State::NotArg} else {unreachable!()}; + state = if chr == ':' { + State::FormatOpts + } else if chr == '}' { + State::NotArg + } else { + unreachable!() + }; } else if chr == '}' { // We're closing one brace met before inside of the expression. current_expr.push(chr); @@ -155,7 +161,7 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec), ()> { // We're inside of braced expression, assume that it's a struct field name/value delimiter. current_expr.push(chr); } - }, + } (State::Ident | State::Expr, '{') => { state = State::Expr; current_expr.push(chr);