about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-04-14 12:36:17 +0000
committerbors <bors@rust-lang.org>2022-04-14 12:36:17 +0000
commit63573d47aaf841abb803af26dadc95e86f1fe572 (patch)
tree929da9ada43127d5062977b3313bf061d65f0212
parent1b15638b4dc0f3ef9c528e2087109ead8af431b1 (diff)
parentf96fd401049f11842b8e934092024566fcabbb8e (diff)
downloadrust-63573d47aaf841abb803af26dadc95e86f1fe572.tar.gz
rust-63573d47aaf841abb803af26dadc95e86f1fe572.zip
Auto merge of #11971 - jonas-schievink:on-type-fmt-assignments, r=jonas-schievink
feat: Add trailing `;` when typing `=` in assignment

![Peek 2022-04-12 19-41](https://user-images.githubusercontent.com/1786438/163022079-1ed114ef-7c75-490f-a8ed-731a13f0b44d.gif)

This does have a false positive to keep in mind, it will add a trailing `;` in the following snippet too, which is probably not desired:

```rust
fn is_zero(i: i32) -> bool {
    i $0 0
}
```

However, that function is unlikely to be written from the "inside out" like that, so it might be acceptable. Typically `=` is only inserted last when the author realizes that an existing expression should be assigned to some variable.
-rw-r--r--crates/ide/src/typing.rs192
1 files changed, 183 insertions, 9 deletions
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 00950127910..a75e6be8b8a 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -39,8 +39,11 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>{";
 // Some features trigger on typing certain characters:
 //
 // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+// - typing `=` between two expressions adds `;` when in statement position
+// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
 // - typing `.` in a chain method call auto-indents
 // - typing `{` in front of an expression inserts a closing `}` after the expression
+// - typing `{` in a use item adds a closing `}` in the right place
 //
 // VS Code::
 //
@@ -166,11 +169,37 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
     if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) {
         return None;
     }
-    let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
-    if let_stmt.semicolon_token().is_some() {
-        return None;
+
+    if let Some(edit) = let_stmt(file, offset) {
+        return Some(edit);
+    }
+    if let Some(edit) = assign_expr(file, offset) {
+        return Some(edit);
+    }
+    if let Some(edit) = assign_to_eq(file, offset) {
+        return Some(edit);
     }
-    if let Some(expr) = let_stmt.initializer() {
+
+    return None;
+
+    fn assign_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+        let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
+        if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) {
+            return None;
+        }
+
+        // Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
+        if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) {
+            if expr_stmt.semicolon_token().is_some() {
+                return None;
+            }
+        } else {
+            if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
+                return None;
+            }
+        }
+
+        let expr = binop.rhs()?;
         let expr_range = expr.syntax().text_range();
         if expr_range.contains(offset) && offset != expr_range.start() {
             return None;
@@ -178,11 +207,45 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
         if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
             return None;
         }
-    } else {
-        return None;
+        let offset = expr.syntax().text_range().end();
+        Some(TextEdit::insert(offset, ";".to_string()))
+    }
+
+    /// `a =$0 b;` removes the semicolon if an expression is valid in this context.
+    fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+        let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
+        if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false })))
+        {
+            return None;
+        }
+
+        let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?;
+        let semi = expr_stmt.semicolon_token()?;
+
+        if expr_stmt.syntax().next_sibling().is_some() {
+            // Not the last statement in the list.
+            return None;
+        }
+
+        Some(TextEdit::delete(semi.text_range()))
+    }
+
+    fn let_stmt(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+        let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
+        if let_stmt.semicolon_token().is_some() {
+            return None;
+        }
+        let expr = let_stmt.initializer()?;
+        let expr_range = expr.syntax().text_range();
+        if expr_range.contains(offset) && offset != expr_range.start() {
+            return None;
+        }
+        if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
+            return None;
+        }
+        let offset = let_stmt.syntax().text_range().end();
+        Some(TextEdit::insert(offset, ";".to_string()))
     }
-    let offset = let_stmt.syntax().text_range().end();
-    Some(TextEdit::insert(offset, ";".to_string()))
 }
 
 /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
@@ -286,7 +349,7 @@ mod tests {
     }
 
     #[test]
-    fn test_on_eq_typed() {
+    fn test_semi_after_let() {
         //     do_check(r"
         // fn foo() {
         //     let foo =$0
@@ -323,6 +386,117 @@ fn foo() {
     }
 
     #[test]
+    fn test_semi_after_assign() {
+        type_char(
+            '=',
+            r#"
+fn f() {
+    i $0 0
+}
+"#,
+            r#"
+fn f() {
+    i = 0;
+}
+"#,
+        );
+        type_char(
+            '=',
+            r#"
+fn f() {
+    i $0 0
+    i
+}
+"#,
+            r#"
+fn f() {
+    i = 0;
+    i
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f(x: u8) {
+    if x $0
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f(x: u8) {
+    if x $0 {}
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f(x: u8) {
+    if x $0 0 {}
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f() {
+    g(i $0 0);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn assign_to_eq() {
+        type_char(
+            '=',
+            r#"
+fn f(a: u8) {
+    a =$0 0;
+}
+"#,
+            r#"
+fn f(a: u8) {
+    a == 0
+}
+"#,
+        );
+        type_char(
+            '=',
+            r#"
+fn f(a: u8) {
+    a $0= 0;
+}
+"#,
+            r#"
+fn f(a: u8) {
+    a == 0
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f(a: u8) {
+    let e = a =$0 0;
+}
+"#,
+        );
+        type_char_noop(
+            '=',
+            r#"
+fn f(a: u8) {
+    let e = a =$0 0;
+    e
+}
+"#,
+        );
+    }
+
+    #[test]
     fn indents_new_chain_call() {
         type_char(
             '.',