Skip to main content

ide/
typing.rs

1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//!     foo
6//!         .bar()
7//!         .baz()
8//!     |   // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
15
16mod on_enter;
17
18use either::Either;
19use hir::EditionedFileId;
20use ide_db::{FilePosition, RootDatabase, base_db::relevant_crates};
21use span::Edition;
22use std::iter;
23
24use syntax::{
25    AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize,
26    algo::{ancestors_at_offset, find_node_at_offset},
27    ast::{self, AstToken, edit::IndentLevel},
28};
29
30use ide_db::text_edit::TextEdit;
31
32use crate::SourceChange;
33
34pub(crate) use on_enter::on_enter;
35
36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &[char] = &['.', '=', '<', '>', '{', '(', '|', '+'];
38
39struct ExtendedTextEdit {
40    edit: TextEdit,
41    is_snippet: bool,
42}
43
44// Feature: On Typing Assists
45//
46// Some features trigger on typing certain characters:
47//
48// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
49// - typing `=` between two expressions adds `;` when in statement position
50// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
51// - typing `.` in a chain method call auto-indents
52// - typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression
53// - typing `{` in a use item adds a closing `}` in the right place
54// - typing `>` to complete a return type `->` will insert a whitespace after it
55//
56// #### VS Code
57//
58// Add the following to `settings.json`:
59// ```json
60// "editor.formatOnType": true,
61// ```
62//
63// ![On Typing Assists](https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif)
64// ![On Typing Assists](https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif)
65pub(crate) fn on_char_typed(
66    db: &RootDatabase,
67    position: FilePosition,
68    char_typed: char,
69) -> Option<SourceChange> {
70    if !TRIGGER_CHARS.contains(&char_typed) {
71        return None;
72    }
73    let edition = relevant_crates(db, position.file_id)
74        .first()
75        .copied()
76        .map_or(Edition::CURRENT, |krate| krate.data(db).edition);
77    let editioned_file_id_wrapper = EditionedFileId::new(db, position.file_id, edition);
78    let file = &editioned_file_id_wrapper.parse(db);
79    let char_matches_position =
80        file.tree().syntax().text().char_at(position.offset) == Some(char_typed);
81    if !stdx::always!(char_matches_position) {
82        return None;
83    }
84
85    let edit = on_char_typed_(file, position.offset, char_typed, edition)?;
86
87    let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit);
88    sc.is_snippet = edit.is_snippet;
89    Some(sc)
90}
91
92fn on_char_typed_(
93    file: &Parse<SourceFile>,
94    offset: TextSize,
95    char_typed: char,
96    edition: Edition,
97) -> Option<ExtendedTextEdit> {
98    match char_typed {
99        '.' => on_dot_typed(&file.tree(), offset),
100        '=' => on_eq_typed(&file.tree(), offset),
101        '>' => on_right_angle_typed(&file.tree(), offset),
102        '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition),
103        '|' => on_pipe_typed(&file.tree(), offset),
104        '+' => on_plus_typed(&file.tree(), offset),
105        _ => None,
106    }
107    .map(conv)
108}
109
110fn conv(edit: TextEdit) -> ExtendedTextEdit {
111    ExtendedTextEdit { edit, is_snippet: false }
112}
113
114/// Inserts a closing delimiter when the user types an opening bracket, wrapping an existing expression in a
115/// block, or a part of a `use` item (for `{`).
116fn on_opening_delimiter_typed(
117    file: &Parse<SourceFile>,
118    offset: TextSize,
119    opening_bracket: char,
120    edition: Edition,
121) -> Option<TextEdit> {
122    type FilterFn = fn(SyntaxKind) -> bool;
123    let (closing_bracket, expected_ast_bracket, allowed_kinds) = match opening_bracket {
124        '{' => ('}', SyntaxKind::L_CURLY, &[ast::Expr::can_cast as FilterFn] as &[FilterFn]),
125        '(' => (
126            ')',
127            SyntaxKind::L_PAREN,
128            &[ast::Expr::can_cast as FilterFn, ast::Pat::can_cast, ast::Type::can_cast]
129                as &[FilterFn],
130        ),
131        '<' => ('>', SyntaxKind::L_ANGLE, &[ast::Type::can_cast as FilterFn] as &[FilterFn]),
132        _ => return None,
133    };
134
135    let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
136    if brace_token.kind() != expected_ast_bracket {
137        return None;
138    }
139
140    // Remove the opening bracket to get a better parse tree, and reparse.
141    let range = brace_token.text_range();
142    if !stdx::always!(range.len() == TextSize::of(opening_bracket)) {
143        return None;
144    }
145    let reparsed = file.reparse(range, "", edition).tree();
146
147    if let Some(edit) =
148        on_delimited_node_typed(&reparsed, offset, opening_bracket, closing_bracket, allowed_kinds)
149    {
150        return Some(edit);
151    }
152
153    match opening_bracket {
154        '{' => on_left_brace_typed(&reparsed, offset),
155        '<' => on_left_angle_typed(&file.tree(), &reparsed, offset),
156        _ => None,
157    }
158}
159
160fn on_left_brace_typed(reparsed: &SourceFile, offset: TextSize) -> Option<TextEdit> {
161    let segment: ast::PathSegment = find_node_at_offset(reparsed.syntax(), offset)?;
162    if segment.syntax().text_range().start() != offset {
163        return None;
164    }
165
166    let tree: ast::UseTree = find_node_at_offset(reparsed.syntax(), offset)?;
167
168    Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned()))
169}
170
171fn on_delimited_node_typed(
172    reparsed: &SourceFile,
173    offset: TextSize,
174    opening_bracket: char,
175    closing_bracket: char,
176    kinds: &[fn(SyntaxKind) -> bool],
177) -> Option<TextEdit> {
178    let t = reparsed.syntax().token_at_offset(offset).right_biased()?;
179    if t.prev_token().is_some_and(|t| t.kind().is_any_identifier()) {
180        return None;
181    }
182    let (filter, node) = t
183        .parent_ancestors()
184        .take_while(|n| n.text_range().start() == offset)
185        .find_map(|n| kinds.iter().find(|&kind_filter| kind_filter(n.kind())).zip(Some(n)))?;
186    let mut node = node
187        .ancestors()
188        .take_while(|n| n.text_range().start() == offset && filter(n.kind()))
189        .last()?;
190
191    if let Some(parent) = node.parent().filter(|it| filter(it.kind())) {
192        let all_prev_sib_attr = {
193            let mut node = node.clone();
194            loop {
195                match node.prev_sibling() {
196                    Some(sib) if sib.kind().is_trivia() || sib.kind() == SyntaxKind::ATTR => {
197                        node = sib
198                    }
199                    Some(_) => break false,
200                    None => break true,
201                };
202            }
203        };
204
205        if all_prev_sib_attr {
206            node = parent;
207        }
208    }
209
210    // Insert the closing bracket right after the node.
211    Some(TextEdit::insert(
212        node.text_range().end() + TextSize::of(opening_bracket),
213        closing_bracket.to_string(),
214    ))
215}
216/// Returns an edit which should be applied after `=` was typed. Primarily,
217/// this works when adding `let =`.
218// FIXME: use a snippet completion instead of this hack here.
219fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
220    let text = file.syntax().text();
221    let has_newline = iter::successors(Some(offset), |&offset| Some(offset + TextSize::new(1)))
222        .filter_map(|offset| text.char_at(offset))
223        .find(|&c| !c.is_whitespace() || c == '\n')
224        == Some('n');
225    // don't attempt to add `;` if there is a newline after the `=`, the intent is likely to write
226    // out the expression afterwards!
227    if has_newline {
228        return None;
229    }
230
231    if let Some(edit) = let_stmt(file, offset) {
232        return Some(edit);
233    }
234    if let Some(edit) = assign_expr(file, offset) {
235        return Some(edit);
236    }
237    if let Some(edit) = assign_to_eq(file, offset) {
238        return Some(edit);
239    }
240
241    return None;
242
243    fn assign_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
244        let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
245        if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) {
246            return None;
247        }
248
249        // Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
250        if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) {
251            if expr_stmt.semicolon_token().is_some() {
252                return None;
253            }
254        } else if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
255            return None;
256        }
257
258        let expr = binop.rhs()?;
259        let expr_range = expr.syntax().text_range();
260        if expr_range.contains(offset) && offset != expr_range.start() {
261            return None;
262        }
263        if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
264            return None;
265        }
266        let offset = expr.syntax().text_range().end();
267        Some(TextEdit::insert(offset, ";".to_owned()))
268    }
269
270    /// `a =$0 b;` removes the semicolon if an expression is valid in this context.
271    fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
272        let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
273        if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false })))
274        {
275            return None;
276        }
277
278        let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?;
279        let semi = expr_stmt.semicolon_token()?;
280
281        if expr_stmt.syntax().next_sibling().is_some() {
282            // Not the last statement in the list.
283            return None;
284        }
285
286        Some(TextEdit::delete(semi.text_range()))
287    }
288
289    fn let_stmt(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
290        let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
291        if let_stmt.semicolon_token().is_some() {
292            return None;
293        }
294        let expr = let_stmt.initializer()?;
295        let expr_range = expr.syntax().text_range();
296        if expr_range.contains(offset) && offset != expr_range.start() {
297            return None;
298        }
299        if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
300            return None;
301        }
302        // Good indicator that we will insert into a bad spot, so bail out.
303        if expr.syntax().descendants().any(|it| it.kind() == SyntaxKind::ERROR) {
304            return None;
305        }
306        let offset = let_stmt.syntax().text_range().end();
307        Some(TextEdit::insert(offset, ";".to_owned()))
308    }
309}
310
311/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
312fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
313    let whitespace =
314        file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
315
316    // if prior is fn call over multiple lines dont indent
317    // or if previous is method call over multiples lines keep that indent
318    let current_indent = {
319        let text = whitespace.text();
320        let (_prefix, suffix) = text.rsplit_once('\n')?;
321        suffix
322    };
323    let current_indent_len = TextSize::of(current_indent);
324
325    let parent = whitespace.syntax().parent()?;
326    // Make sure dot is a part of call chain
327    let receiver = if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
328        field_expr.expr()?
329    } else {
330        ast::MethodCallExpr::cast(parent.clone())?.receiver()?
331    };
332
333    let receiver_is_multiline = receiver.syntax().text().find_char('\n').is_some();
334    let target_indent = match (receiver, receiver_is_multiline) {
335        // if receiver is multiline field or method call, just take the previous `.` indentation
336        (ast::Expr::MethodCallExpr(expr), true) => {
337            expr.dot_token().as_ref().map(IndentLevel::from_token)
338        }
339        (ast::Expr::FieldExpr(expr), true) => {
340            expr.dot_token().as_ref().map(IndentLevel::from_token)
341        }
342        // if receiver is multiline expression, just keeps its indentation
343        (_, true) => Some(IndentLevel::from_node(&parent)),
344        _ => None,
345    };
346    let target_indent = match target_indent {
347        Some(x) => x,
348        // in all other cases, take previous indentation and indent once
349        None => IndentLevel::from_node(&parent) + 1,
350    }
351    .to_string();
352
353    if current_indent_len == TextSize::of(&target_indent) {
354        return None;
355    }
356
357    Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
358}
359
360/// Add closing `>` for generic arguments/parameters.
361fn on_left_angle_typed(
362    file: &SourceFile,
363    reparsed: &SourceFile,
364    offset: TextSize,
365) -> Option<TextEdit> {
366    let file_text = reparsed.syntax().text();
367
368    // Find the next non-whitespace char in the line, check if its a `>`
369    let mut next_offset = offset;
370    while file_text.char_at(next_offset) == Some(' ') {
371        next_offset += TextSize::of(' ')
372    }
373    if file_text.char_at(next_offset) == Some('>') {
374        return None;
375    }
376
377    if ancestors_at_offset(file.syntax(), offset)
378        .take_while(|n| !ast::Item::can_cast(n.kind()))
379        .any(|n| {
380            ast::GenericParamList::can_cast(n.kind())
381                || ast::GenericArgList::can_cast(n.kind())
382                || ast::UseBoundGenericArgs::can_cast(n.kind())
383        })
384    {
385        // Insert the closing bracket right after
386        Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string()))
387    } else {
388        None
389    }
390}
391
392fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
393    let pipe_token = file.syntax().token_at_offset(offset).right_biased()?;
394    if pipe_token.kind() != SyntaxKind::PIPE {
395        return None;
396    }
397    if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() {
398        return None;
399    }
400    let after_lpipe = offset + TextSize::of('|');
401    Some(TextEdit::insert(after_lpipe, "|".to_owned()))
402}
403
404fn on_plus_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
405    let plus_token = file.syntax().token_at_offset(offset).right_biased()?;
406    if plus_token.kind() != SyntaxKind::PLUS {
407        return None;
408    }
409    let mut ancestors = plus_token.parent_ancestors();
410    ancestors.next().and_then(ast::TypeBoundList::cast)?;
411    let trait_type =
412        ancestors.next().and_then(<Either<ast::DynTraitType, ast::ImplTraitType>>::cast)?;
413    let kind = ancestors.next()?.kind();
414
415    if ast::RefType::can_cast(kind) || ast::PtrType::can_cast(kind) || ast::RetType::can_cast(kind)
416    {
417        let mut builder = TextEdit::builder();
418        builder.insert(trait_type.syntax().text_range().start(), "(".to_owned());
419        builder.insert(trait_type.syntax().text_range().end(), ")".to_owned());
420        Some(builder.finish())
421    } else {
422        None
423    }
424}
425
426/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
427fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
428    let file_text = file.syntax().text();
429    let after_arrow = offset + TextSize::of('>');
430    if file_text.char_at(after_arrow) != Some('{') {
431        return None;
432    }
433    find_node_at_offset::<ast::RetType>(file.syntax(), offset)?;
434
435    Some(TextEdit::insert(after_arrow, " ".to_owned()))
436}
437
438#[cfg(test)]
439mod tests {
440    use test_utils::{assert_eq_text, extract_offset};
441
442    use super::*;
443
444    impl ExtendedTextEdit {
445        fn apply(&self, text: &mut String) {
446            self.edit.apply(text);
447        }
448    }
449
450    fn do_type_char(char_typed: char, before: &str) -> Option<String> {
451        let (offset, mut before) = extract_offset(before);
452        let edit = TextEdit::insert(offset, char_typed.to_string());
453        edit.apply(&mut before);
454        let parse = SourceFile::parse(&before, span::Edition::CURRENT);
455        on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT).map(|it| {
456            it.apply(&mut before);
457            before.to_string()
458        })
459    }
460
461    fn type_char(
462        char_typed: char,
463        #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
464        #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
465    ) {
466        let actual = do_type_char(char_typed, ra_fixture_before)
467            .unwrap_or_else(|| panic!("typing `{char_typed}` did nothing"));
468
469        assert_eq_text!(ra_fixture_after, &actual);
470    }
471
472    fn type_char_noop(char_typed: char, #[rust_analyzer::rust_fixture] ra_fixture_before: &str) {
473        let file_change = do_type_char(char_typed, ra_fixture_before);
474        assert_eq!(file_change, None)
475    }
476
477    #[test]
478    fn test_semi_after_let() {
479        type_char_noop(
480            '=',
481            r"
482fn foo() {
483    let foo =$0
484}
485",
486        );
487        type_char(
488            '=',
489            r#"
490fn foo() {
491    let foo $0 1 + 1
492}
493"#,
494            r#"
495fn foo() {
496    let foo = 1 + 1;
497}
498"#,
499        );
500        type_char_noop(
501            '=',
502            r#"
503fn foo() {
504    let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 {
505        // ...
506    }
507}
508"#,
509        );
510        type_char_noop(
511            '=',
512            r"
513fn foo() {
514    let foo =$0
515    let bar = 1;
516}
517",
518        );
519        type_char_noop(
520            '=',
521            r"
522fn foo() {
523    let foo =$0
524     1 + 1
525}
526",
527        );
528    }
529
530    #[test]
531    fn test_semi_after_assign() {
532        type_char(
533            '=',
534            r#"
535fn f() {
536    i $0 0
537}
538"#,
539            r#"
540fn f() {
541    i = 0;
542}
543"#,
544        );
545        type_char(
546            '=',
547            r#"
548fn f() {
549    i $0 0
550    i
551}
552"#,
553            r#"
554fn f() {
555    i = 0;
556    i
557}
558"#,
559        );
560        type_char_noop(
561            '=',
562            r#"
563fn f(x: u8) {
564    if x $0
565}
566"#,
567        );
568        type_char_noop(
569            '=',
570            r#"
571fn f(x: u8) {
572    if x $0 {}
573}
574"#,
575        );
576        type_char_noop(
577            '=',
578            r#"
579fn f(x: u8) {
580    if x $0 0 {}
581}
582"#,
583        );
584        type_char_noop(
585            '=',
586            r#"
587fn f() {
588    g(i $0 0);
589}
590"#,
591        );
592    }
593
594    #[test]
595    fn assign_to_eq() {
596        type_char(
597            '=',
598            r#"
599fn f(a: u8) {
600    a =$0 0;
601}
602"#,
603            r#"
604fn f(a: u8) {
605    a == 0
606}
607"#,
608        );
609        type_char(
610            '=',
611            r#"
612fn f(a: u8) {
613    a $0= 0;
614}
615"#,
616            r#"
617fn f(a: u8) {
618    a == 0
619}
620"#,
621        );
622        type_char_noop(
623            '=',
624            r#"
625fn f(a: u8) {
626    let e = a =$0 0;
627}
628"#,
629        );
630        type_char_noop(
631            '=',
632            r#"
633fn f(a: u8) {
634    let e = a =$0 0;
635    e
636}
637"#,
638        );
639    }
640
641    #[test]
642    fn indents_new_chain_call() {
643        type_char(
644            '.',
645            r#"
646fn main() {
647    xs.foo()
648    $0
649}
650            "#,
651            r#"
652fn main() {
653    xs.foo()
654        .
655}
656            "#,
657        );
658        type_char_noop(
659            '.',
660            r#"
661fn main() {
662    xs.foo()
663        $0
664}
665            "#,
666        )
667    }
668
669    #[test]
670    fn indents_new_chain_call_with_semi() {
671        type_char(
672            '.',
673            r"
674fn main() {
675    xs.foo()
676    $0;
677}
678            ",
679            r#"
680fn main() {
681    xs.foo()
682        .;
683}
684            "#,
685        );
686        type_char_noop(
687            '.',
688            r#"
689fn main() {
690    xs.foo()
691        $0;
692}
693            "#,
694        )
695    }
696
697    #[test]
698    fn indents_new_chain_call_with_let() {
699        type_char(
700            '.',
701            r#"
702fn main() {
703    let _ = foo
704    $0
705    bar()
706}
707"#,
708            r#"
709fn main() {
710    let _ = foo
711        .
712    bar()
713}
714"#,
715        );
716    }
717
718    #[test]
719    fn indents_continued_chain_call() {
720        type_char(
721            '.',
722            r#"
723fn main() {
724    xs.foo()
725        .first()
726    $0
727}
728            "#,
729            r#"
730fn main() {
731    xs.foo()
732        .first()
733        .
734}
735            "#,
736        );
737        type_char_noop(
738            '.',
739            r#"
740fn main() {
741    xs.foo()
742        .first()
743        $0
744}
745            "#,
746        );
747    }
748
749    #[test]
750    fn indents_middle_of_chain_call() {
751        type_char(
752            '.',
753            r#"
754fn source_impl() {
755    let var = enum_defvariant_list().unwrap()
756    $0
757        .nth(92)
758        .unwrap();
759}
760            "#,
761            r#"
762fn source_impl() {
763    let var = enum_defvariant_list().unwrap()
764        .
765        .nth(92)
766        .unwrap();
767}
768            "#,
769        );
770        type_char_noop(
771            '.',
772            r#"
773fn source_impl() {
774    let var = enum_defvariant_list().unwrap()
775        $0
776        .nth(92)
777        .unwrap();
778}
779            "#,
780        );
781    }
782
783    #[test]
784    fn dont_indent_freestanding_dot() {
785        type_char_noop(
786            '.',
787            r#"
788fn main() {
789    $0
790}
791            "#,
792        );
793        type_char_noop(
794            '.',
795            r#"
796fn main() {
797$0
798}
799            "#,
800        );
801    }
802
803    #[test]
804    fn adds_space_after_return_type() {
805        type_char(
806            '>',
807            r#"
808fn foo() -$0{ 92 }
809"#,
810            r#"
811fn foo() -> { 92 }
812"#,
813        );
814    }
815
816    #[test]
817    fn adds_closing_brace_for_expr() {
818        type_char(
819            '{',
820            r#"
821fn f() { match () { _ => $0() } }
822            "#,
823            r#"
824fn f() { match () { _ => {()} } }
825            "#,
826        );
827        type_char(
828            '{',
829            r#"
830fn f() { $0() }
831            "#,
832            r#"
833fn f() { {()} }
834            "#,
835        );
836        type_char(
837            '{',
838            r#"
839fn f() { let x = $0(); }
840            "#,
841            r#"
842fn f() { let x = {()}; }
843            "#,
844        );
845        type_char(
846            '{',
847            r#"
848fn f() { let x = $0a.b(); }
849            "#,
850            r#"
851fn f() { let x = {a.b()}; }
852            "#,
853        );
854        type_char(
855            '{',
856            r#"
857const S: () = $0();
858fn f() {}
859            "#,
860            r#"
861const S: () = {()};
862fn f() {}
863            "#,
864        );
865        type_char(
866            '{',
867            r#"
868const S: () = $0a.b();
869fn f() {}
870            "#,
871            r#"
872const S: () = {a.b()};
873fn f() {}
874            "#,
875        );
876        type_char(
877            '{',
878            r#"
879fn f() {
880    match x {
881        0 => $0(),
882        1 => (),
883    }
884}
885            "#,
886            r#"
887fn f() {
888    match x {
889        0 => {()},
890        1 => (),
891    }
892}
893            "#,
894        );
895        type_char(
896            '{',
897            r#"
898fn main() {
899    #[allow(unreachable_code)]
900    $0g();
901}
902            "#,
903            r#"
904fn main() {
905    #[allow(unreachable_code)]
906    {g()};
907}
908            "#,
909        );
910    }
911
912    #[test]
913    fn noop_in_string_literal() {
914        // Regression test for #9351
915        type_char_noop(
916            '{',
917            r##"
918fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
919    let base = r#"
920enum E { T(), R$0, C }
921use self::E::X;
922const Z: E = E::C;
923mod m {}
924asdasdasdasdasdasda
925sdasdasdasdasdasda
926sdasdasdasdasd
927"#;
928    let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
929    expect.assert_eq(&actual)
930}
931            "##,
932        );
933    }
934
935    #[test]
936    fn noop_in_item_position_with_macro() {
937        type_char_noop('{', r#"$0println!();"#);
938        type_char_noop(
939            '{',
940            r#"
941fn main() $0println!("hello");
942}"#,
943        );
944    }
945
946    #[test]
947    fn adds_closing_brace_for_use_tree() {
948        type_char(
949            '{',
950            r#"
951use some::$0Path;
952            "#,
953            r#"
954use some::{Path};
955            "#,
956        );
957        type_char(
958            '{',
959            r#"
960use some::{Path, $0Other};
961            "#,
962            r#"
963use some::{Path, {Other}};
964            "#,
965        );
966        type_char(
967            '{',
968            r#"
969use some::{$0Path, Other};
970            "#,
971            r#"
972use some::{{Path}, Other};
973            "#,
974        );
975        type_char(
976            '{',
977            r#"
978use some::path::$0to::Item;
979            "#,
980            r#"
981use some::path::{to::Item};
982            "#,
983        );
984        type_char(
985            '{',
986            r#"
987use some::$0path::to::Item;
988            "#,
989            r#"
990use some::{path::to::Item};
991            "#,
992        );
993        type_char(
994            '{',
995            r#"
996use $0some::path::to::Item;
997            "#,
998            r#"
999use {some::path::to::Item};
1000            "#,
1001        );
1002        type_char(
1003            '{',
1004            r#"
1005use some::path::$0to::{Item};
1006            "#,
1007            r#"
1008use some::path::{to::{Item}};
1009            "#,
1010        );
1011        type_char(
1012            '{',
1013            r#"
1014use $0Thing as _;
1015            "#,
1016            r#"
1017use {Thing as _};
1018            "#,
1019        );
1020
1021        type_char_noop(
1022            '{',
1023            r#"
1024use some::pa$0th::to::Item;
1025            "#,
1026        );
1027    }
1028
1029    #[test]
1030    fn adds_closing_parenthesis_for_expr() {
1031        type_char(
1032            '(',
1033            r#"
1034fn f() { match () { _ => $0() } }
1035            "#,
1036            r#"
1037fn f() { match () { _ => (()) } }
1038            "#,
1039        );
1040        type_char(
1041            '(',
1042            r#"
1043fn f() { $0() }
1044            "#,
1045            r#"
1046fn f() { (()) }
1047            "#,
1048        );
1049        type_char(
1050            '(',
1051            r#"
1052fn f() { let x = $0(); }
1053            "#,
1054            r#"
1055fn f() { let x = (()); }
1056            "#,
1057        );
1058        type_char(
1059            '(',
1060            r#"
1061fn f() { let x = $0a.b(); }
1062            "#,
1063            r#"
1064fn f() { let x = (a.b()); }
1065            "#,
1066        );
1067        type_char(
1068            '(',
1069            r#"
1070const S: () = $0();
1071fn f() {}
1072            "#,
1073            r#"
1074const S: () = (());
1075fn f() {}
1076            "#,
1077        );
1078        type_char(
1079            '(',
1080            r#"
1081const S: () = $0a.b();
1082fn f() {}
1083            "#,
1084            r#"
1085const S: () = (a.b());
1086fn f() {}
1087            "#,
1088        );
1089        type_char(
1090            '(',
1091            r#"
1092fn f() {
1093    match x {
1094        0 => $0(),
1095        1 => (),
1096    }
1097}
1098            "#,
1099            r#"
1100fn f() {
1101    match x {
1102        0 => (()),
1103        1 => (),
1104    }
1105}
1106            "#,
1107        );
1108        type_char(
1109            '(',
1110            r#"
1111        fn f() {
1112            let z = Some($03);
1113        }
1114                    "#,
1115            r#"
1116        fn f() {
1117            let z = Some((3));
1118        }
1119                    "#,
1120        );
1121    }
1122
1123    #[test]
1124    fn preceding_whitespace_is_significant_for_closing_brackets() {
1125        type_char_noop(
1126            '(',
1127            r#"
1128fn f() { a.b$0if true {} }
1129"#,
1130        );
1131        type_char_noop(
1132            '(',
1133            r#"
1134fn f() { foo$0{} }
1135"#,
1136        );
1137    }
1138
1139    #[test]
1140    fn adds_closing_parenthesis_for_pat() {
1141        type_char(
1142            '(',
1143            r#"
1144fn f() { match () { $0() => () } }
1145"#,
1146            r#"
1147fn f() { match () { (()) => () } }
1148"#,
1149        );
1150        type_char(
1151            '(',
1152            r#"
1153fn f($0n: ()) {}
1154"#,
1155            r#"
1156fn f((n): ()) {}
1157"#,
1158        );
1159    }
1160
1161    #[test]
1162    fn adds_closing_parenthesis_for_ty() {
1163        type_char(
1164            '(',
1165            r#"
1166fn f(n: $0()) {}
1167"#,
1168            r#"
1169fn f(n: (())) {}
1170"#,
1171        );
1172        type_char(
1173            '(',
1174            r#"
1175fn f(n: $0a::b::<d>::c) {}
1176"#,
1177            r#"
1178fn f(n: (a::b::<d>::c)) {}
1179"#,
1180        );
1181    }
1182
1183    #[test]
1184    fn adds_closing_angles_for_ty() {
1185        type_char(
1186            '<',
1187            r#"
1188fn f(n: $0()) {}
1189"#,
1190            r#"
1191fn f(n: <()>) {}
1192"#,
1193        );
1194        type_char(
1195            '<',
1196            r#"
1197fn f(n: $0a::b::<d>::c) {}
1198"#,
1199            r#"
1200fn f(n: <a::b::<d>::c>) {}
1201"#,
1202        );
1203        type_char(
1204            '<',
1205            r#"
1206fn f(n: a$0b::<d>::c) {}
1207"#,
1208            r#"
1209fn f(n: a<>b::<d>::c) {}
1210"#,
1211        );
1212    }
1213
1214    #[test]
1215    fn parenthesis_noop_in_string_literal() {
1216        // Regression test for #9351
1217        type_char_noop(
1218            '(',
1219            r##"
1220fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
1221    let base = r#"
1222enum E { T(), R$0, C }
1223use self::E::X;
1224const Z: E = E::C;
1225mod m {}
1226asdasdasdasdasdasda
1227sdasdasdasdasdasda
1228sdasdasdasdasd
1229"#;
1230    let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
1231    expect.assert_eq(&actual)
1232}
1233            "##,
1234        );
1235    }
1236
1237    #[test]
1238    fn parenthesis_noop_in_item_position_with_macro() {
1239        type_char_noop('(', r#"$0println!();"#);
1240    }
1241
1242    #[test]
1243    fn parenthesis_noop_in_use_tree() {
1244        type_char_noop(
1245            '(',
1246            r#"
1247use some::$0Path;
1248            "#,
1249        );
1250        type_char_noop(
1251            '(',
1252            r#"
1253use some::{Path, $0Other};
1254            "#,
1255        );
1256        type_char_noop(
1257            '(',
1258            r#"
1259use some::{$0Path, Other};
1260            "#,
1261        );
1262        type_char_noop(
1263            '(',
1264            r#"
1265use some::path::$0to::Item;
1266            "#,
1267        );
1268        type_char_noop(
1269            '(',
1270            r#"
1271use some::$0path::to::Item;
1272            "#,
1273        );
1274        type_char_noop(
1275            '(',
1276            r#"
1277use $0some::path::to::Item;
1278            "#,
1279        );
1280        type_char_noop(
1281            '(',
1282            r#"
1283use some::path::$0to::{Item};
1284            "#,
1285        );
1286        type_char_noop(
1287            '(',
1288            r#"
1289use $0Thing as _;
1290            "#,
1291        );
1292
1293        type_char_noop(
1294            '(',
1295            r#"
1296use some::pa$0th::to::Item;
1297            "#,
1298        );
1299        type_char_noop(
1300            '<',
1301            r#"
1302use some::pa$0th::to::Item;
1303            "#,
1304        );
1305    }
1306
1307    #[test]
1308    fn adds_closing_angle_bracket_for_generic_args() {
1309        type_char(
1310            '<',
1311            r#"
1312fn foo() {
1313    bar::$0
1314}
1315            "#,
1316            r#"
1317fn foo() {
1318    bar::<>
1319}
1320            "#,
1321        );
1322
1323        type_char(
1324            '<',
1325            r#"
1326fn foo(bar: &[u64]) {
1327    bar.iter().collect::$0();
1328}
1329            "#,
1330            r#"
1331fn foo(bar: &[u64]) {
1332    bar.iter().collect::<>();
1333}
1334            "#,
1335        );
1336    }
1337
1338    #[test]
1339    fn adds_closing_angle_bracket_for_generic_params() {
1340        type_char(
1341            '<',
1342            r#"
1343fn foo$0() {}
1344            "#,
1345            r#"
1346fn foo<>() {}
1347            "#,
1348        );
1349        type_char(
1350            '<',
1351            r#"
1352fn foo$0
1353            "#,
1354            r#"
1355fn foo<>
1356            "#,
1357        );
1358        type_char(
1359            '<',
1360            r#"
1361struct Foo$0 {}
1362            "#,
1363            r#"
1364struct Foo<> {}
1365            "#,
1366        );
1367        type_char(
1368            '<',
1369            r#"
1370struct Foo$0();
1371            "#,
1372            r#"
1373struct Foo<>();
1374            "#,
1375        );
1376        type_char(
1377            '<',
1378            r#"
1379struct Foo$0
1380            "#,
1381            r#"
1382struct Foo<>
1383            "#,
1384        );
1385        type_char(
1386            '<',
1387            r#"
1388enum Foo$0
1389            "#,
1390            r#"
1391enum Foo<>
1392            "#,
1393        );
1394        type_char(
1395            '<',
1396            r#"
1397trait Foo$0
1398            "#,
1399            r#"
1400trait Foo<>
1401            "#,
1402        );
1403        type_char(
1404            '<',
1405            r#"
1406type Foo$0 = Bar;
1407            "#,
1408            r#"
1409type Foo<> = Bar;
1410            "#,
1411        );
1412        type_char(
1413            '<',
1414            r#"
1415impl<T> Foo$0 {}
1416            "#,
1417            r#"
1418impl<T> Foo<> {}
1419            "#,
1420        );
1421        type_char(
1422            '<',
1423            r#"
1424impl Foo$0 {}
1425            "#,
1426            r#"
1427impl Foo<> {}
1428            "#,
1429        );
1430    }
1431
1432    #[test]
1433    fn dont_add_closing_angle_bracket_for_comparison() {
1434        type_char_noop(
1435            '<',
1436            r#"
1437fn main() {
1438    42$0
1439}
1440            "#,
1441        );
1442        type_char_noop(
1443            '<',
1444            r#"
1445fn main() {
1446    42 $0
1447}
1448            "#,
1449        );
1450        type_char_noop(
1451            '<',
1452            r#"
1453fn main() {
1454    let foo = 42;
1455    foo $0
1456}
1457            "#,
1458        );
1459    }
1460
1461    #[test]
1462    fn dont_add_closing_angle_bracket_if_it_is_already_there() {
1463        type_char_noop(
1464            '<',
1465            r#"
1466fn foo() {
1467    bar::$0>
1468}
1469            "#,
1470        );
1471        type_char_noop(
1472            '<',
1473            r#"
1474fn foo(bar: &[u64]) {
1475    bar.iter().collect::$0   >();
1476}
1477            "#,
1478        );
1479        type_char_noop(
1480            '<',
1481            r#"
1482fn foo$0>() {}
1483            "#,
1484        );
1485        type_char_noop(
1486            '<',
1487            r#"
1488fn foo$0>
1489            "#,
1490        );
1491        type_char_noop(
1492            '<',
1493            r#"
1494struct Foo$0> {}
1495            "#,
1496        );
1497        type_char_noop(
1498            '<',
1499            r#"
1500struct Foo$0>();
1501            "#,
1502        );
1503        type_char_noop(
1504            '<',
1505            r#"
1506struct Foo$0>
1507            "#,
1508        );
1509        type_char_noop(
1510            '<',
1511            r#"
1512enum Foo$0>
1513            "#,
1514        );
1515        type_char_noop(
1516            '<',
1517            r#"
1518trait Foo$0>
1519            "#,
1520        );
1521        type_char_noop(
1522            '<',
1523            r#"
1524type Foo$0> = Bar;
1525            "#,
1526        );
1527        type_char_noop(
1528            '<',
1529            r#"
1530impl$0> Foo {}
1531            "#,
1532        );
1533        type_char_noop(
1534            '<',
1535            r#"
1536impl<T> Foo$0> {}
1537            "#,
1538        );
1539        type_char_noop(
1540            '<',
1541            r#"
1542impl Foo$0> {}
1543            "#,
1544        );
1545    }
1546
1547    #[test]
1548    fn regression_629() {
1549        type_char_noop(
1550            '.',
1551            r#"
1552fn foo() {
1553    CompletionItem::new(
1554        CompletionKind::Reference,
1555        ctx.source_range(),
1556        field.name().to_string(),
1557    )
1558    .foo()
1559    $0
1560}
1561"#,
1562        );
1563        type_char_noop(
1564            '.',
1565            r#"
1566fn foo() {
1567    CompletionItem::new(
1568        CompletionKind::Reference,
1569        ctx.source_range(),
1570        field.name().to_string(),
1571    )
1572    $0
1573}
1574"#,
1575        );
1576    }
1577
1578    #[test]
1579    fn completes_pipe_param_list() {
1580        type_char(
1581            '|',
1582            r#"
1583fn foo() {
1584    $0
1585}
1586"#,
1587            r#"
1588fn foo() {
1589    ||
1590}
1591"#,
1592        );
1593        type_char(
1594            '|',
1595            r#"
1596fn foo() {
1597    $0 a
1598}
1599"#,
1600            r#"
1601fn foo() {
1602    || a
1603}
1604"#,
1605        );
1606        type_char_noop(
1607            '|',
1608            r#"
1609fn foo() {
1610    let $0
1611}
1612"#,
1613        );
1614    }
1615
1616    #[test]
1617    fn adds_parentheses_around_trait_object_in_ref_type() {
1618        type_char(
1619            '+',
1620            r#"
1621fn foo(x: &dyn A$0) {}
1622"#,
1623            r#"
1624fn foo(x: &(dyn A+)) {}
1625"#,
1626        );
1627        type_char(
1628            '+',
1629            r#"
1630fn foo(x: &'static dyn A$0B) {}
1631"#,
1632            r#"
1633fn foo(x: &'static (dyn A+B)) {}
1634"#,
1635        );
1636        type_char_noop(
1637            '+',
1638            r#"
1639fn foo(x: &(dyn A$0)) {}
1640"#,
1641        );
1642        type_char_noop(
1643            '+',
1644            r#"
1645fn foo(x: Box<dyn A$0>) {}
1646"#,
1647        );
1648    }
1649
1650    #[test]
1651    fn adds_parentheses_around_trait_object_in_ptr_type() {
1652        type_char(
1653            '+',
1654            r#"
1655fn foo(x: *const dyn A$0) {}
1656"#,
1657            r#"
1658fn foo(x: *const (dyn A+)) {}
1659"#,
1660        );
1661    }
1662
1663    #[test]
1664    fn adds_parentheses_around_trait_object_in_return_type() {
1665        type_char(
1666            '+',
1667            r#"
1668fn foo(x: fn() -> dyn A$0) {}
1669"#,
1670            r#"
1671fn foo(x: fn() -> (dyn A+)) {}
1672"#,
1673        );
1674    }
1675}