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 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent.clone()) {
330        method_call_expr.receiver()?
331    } else {
332        return None;
333    };
334
335    let receiver_is_multiline = receiver.syntax().text().find_char('\n').is_some();
336    let target_indent = match (receiver, receiver_is_multiline) {
337        // if receiver is multiline field or method call, just take the previous `.` indentation
338        (ast::Expr::MethodCallExpr(expr), true) => {
339            expr.dot_token().as_ref().map(IndentLevel::from_token)
340        }
341        (ast::Expr::FieldExpr(expr), true) => {
342            expr.dot_token().as_ref().map(IndentLevel::from_token)
343        }
344        // if receiver is multiline expression, just keeps its indentation
345        (_, true) => Some(IndentLevel::from_node(&parent)),
346        _ => None,
347    };
348    let target_indent = match target_indent {
349        Some(x) => x,
350        // in all other cases, take previous indentation and indent once
351        None => IndentLevel::from_node(&parent) + 1,
352    }
353    .to_string();
354
355    if current_indent_len == TextSize::of(&target_indent) {
356        return None;
357    }
358
359    Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
360}
361
362/// Add closing `>` for generic arguments/parameters.
363fn on_left_angle_typed(
364    file: &SourceFile,
365    reparsed: &SourceFile,
366    offset: TextSize,
367) -> Option<TextEdit> {
368    let file_text = reparsed.syntax().text();
369
370    // Find the next non-whitespace char in the line, check if its a `>`
371    let mut next_offset = offset;
372    while file_text.char_at(next_offset) == Some(' ') {
373        next_offset += TextSize::of(' ')
374    }
375    if file_text.char_at(next_offset) == Some('>') {
376        return None;
377    }
378
379    if ancestors_at_offset(file.syntax(), offset)
380        .take_while(|n| !ast::Item::can_cast(n.kind()))
381        .any(|n| {
382            ast::GenericParamList::can_cast(n.kind())
383                || ast::GenericArgList::can_cast(n.kind())
384                || ast::UseBoundGenericArgs::can_cast(n.kind())
385        })
386    {
387        // Insert the closing bracket right after
388        Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string()))
389    } else {
390        None
391    }
392}
393
394fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
395    let pipe_token = file.syntax().token_at_offset(offset).right_biased()?;
396    if pipe_token.kind() != SyntaxKind::PIPE {
397        return None;
398    }
399    if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() {
400        return None;
401    }
402    let after_lpipe = offset + TextSize::of('|');
403    Some(TextEdit::insert(after_lpipe, "|".to_owned()))
404}
405
406fn on_plus_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
407    let plus_token = file.syntax().token_at_offset(offset).right_biased()?;
408    if plus_token.kind() != SyntaxKind::PLUS {
409        return None;
410    }
411    let mut ancestors = plus_token.parent_ancestors();
412    ancestors.next().and_then(ast::TypeBoundList::cast)?;
413    let trait_type =
414        ancestors.next().and_then(<Either<ast::DynTraitType, ast::ImplTraitType>>::cast)?;
415    let kind = ancestors.next()?.kind();
416
417    if ast::RefType::can_cast(kind) || ast::PtrType::can_cast(kind) || ast::RetType::can_cast(kind)
418    {
419        let mut builder = TextEdit::builder();
420        builder.insert(trait_type.syntax().text_range().start(), "(".to_owned());
421        builder.insert(trait_type.syntax().text_range().end(), ")".to_owned());
422        Some(builder.finish())
423    } else {
424        None
425    }
426}
427
428/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
429fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
430    let file_text = file.syntax().text();
431    let after_arrow = offset + TextSize::of('>');
432    if file_text.char_at(after_arrow) != Some('{') {
433        return None;
434    }
435    find_node_at_offset::<ast::RetType>(file.syntax(), offset)?;
436
437    Some(TextEdit::insert(after_arrow, " ".to_owned()))
438}
439
440#[cfg(test)]
441mod tests {
442    use test_utils::{assert_eq_text, extract_offset};
443
444    use super::*;
445
446    impl ExtendedTextEdit {
447        fn apply(&self, text: &mut String) {
448            self.edit.apply(text);
449        }
450    }
451
452    fn do_type_char(char_typed: char, before: &str) -> Option<String> {
453        let (offset, mut before) = extract_offset(before);
454        let edit = TextEdit::insert(offset, char_typed.to_string());
455        edit.apply(&mut before);
456        let parse = SourceFile::parse(&before, span::Edition::CURRENT);
457        on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT).map(|it| {
458            it.apply(&mut before);
459            before.to_string()
460        })
461    }
462
463    fn type_char(
464        char_typed: char,
465        #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
466        #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
467    ) {
468        let actual = do_type_char(char_typed, ra_fixture_before)
469            .unwrap_or_else(|| panic!("typing `{char_typed}` did nothing"));
470
471        assert_eq_text!(ra_fixture_after, &actual);
472    }
473
474    fn type_char_noop(char_typed: char, #[rust_analyzer::rust_fixture] ra_fixture_before: &str) {
475        let file_change = do_type_char(char_typed, ra_fixture_before);
476        assert_eq!(file_change, None)
477    }
478
479    #[test]
480    fn test_semi_after_let() {
481        type_char_noop(
482            '=',
483            r"
484fn foo() {
485    let foo =$0
486}
487",
488        );
489        type_char(
490            '=',
491            r#"
492fn foo() {
493    let foo $0 1 + 1
494}
495"#,
496            r#"
497fn foo() {
498    let foo = 1 + 1;
499}
500"#,
501        );
502        type_char_noop(
503            '=',
504            r#"
505fn foo() {
506    let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 {
507        // ...
508    }
509}
510"#,
511        );
512        type_char_noop(
513            '=',
514            r"
515fn foo() {
516    let foo =$0
517    let bar = 1;
518}
519",
520        );
521        type_char_noop(
522            '=',
523            r"
524fn foo() {
525    let foo =$0
526     1 + 1
527}
528",
529        );
530    }
531
532    #[test]
533    fn test_semi_after_assign() {
534        type_char(
535            '=',
536            r#"
537fn f() {
538    i $0 0
539}
540"#,
541            r#"
542fn f() {
543    i = 0;
544}
545"#,
546        );
547        type_char(
548            '=',
549            r#"
550fn f() {
551    i $0 0
552    i
553}
554"#,
555            r#"
556fn f() {
557    i = 0;
558    i
559}
560"#,
561        );
562        type_char_noop(
563            '=',
564            r#"
565fn f(x: u8) {
566    if x $0
567}
568"#,
569        );
570        type_char_noop(
571            '=',
572            r#"
573fn f(x: u8) {
574    if x $0 {}
575}
576"#,
577        );
578        type_char_noop(
579            '=',
580            r#"
581fn f(x: u8) {
582    if x $0 0 {}
583}
584"#,
585        );
586        type_char_noop(
587            '=',
588            r#"
589fn f() {
590    g(i $0 0);
591}
592"#,
593        );
594    }
595
596    #[test]
597    fn assign_to_eq() {
598        type_char(
599            '=',
600            r#"
601fn f(a: u8) {
602    a =$0 0;
603}
604"#,
605            r#"
606fn f(a: u8) {
607    a == 0
608}
609"#,
610        );
611        type_char(
612            '=',
613            r#"
614fn f(a: u8) {
615    a $0= 0;
616}
617"#,
618            r#"
619fn f(a: u8) {
620    a == 0
621}
622"#,
623        );
624        type_char_noop(
625            '=',
626            r#"
627fn f(a: u8) {
628    let e = a =$0 0;
629}
630"#,
631        );
632        type_char_noop(
633            '=',
634            r#"
635fn f(a: u8) {
636    let e = a =$0 0;
637    e
638}
639"#,
640        );
641    }
642
643    #[test]
644    fn indents_new_chain_call() {
645        type_char(
646            '.',
647            r#"
648fn main() {
649    xs.foo()
650    $0
651}
652            "#,
653            r#"
654fn main() {
655    xs.foo()
656        .
657}
658            "#,
659        );
660        type_char_noop(
661            '.',
662            r#"
663fn main() {
664    xs.foo()
665        $0
666}
667            "#,
668        )
669    }
670
671    #[test]
672    fn indents_new_chain_call_with_semi() {
673        type_char(
674            '.',
675            r"
676fn main() {
677    xs.foo()
678    $0;
679}
680            ",
681            r#"
682fn main() {
683    xs.foo()
684        .;
685}
686            "#,
687        );
688        type_char_noop(
689            '.',
690            r#"
691fn main() {
692    xs.foo()
693        $0;
694}
695            "#,
696        )
697    }
698
699    #[test]
700    fn indents_new_chain_call_with_let() {
701        type_char(
702            '.',
703            r#"
704fn main() {
705    let _ = foo
706    $0
707    bar()
708}
709"#,
710            r#"
711fn main() {
712    let _ = foo
713        .
714    bar()
715}
716"#,
717        );
718    }
719
720    #[test]
721    fn indents_continued_chain_call() {
722        type_char(
723            '.',
724            r#"
725fn main() {
726    xs.foo()
727        .first()
728    $0
729}
730            "#,
731            r#"
732fn main() {
733    xs.foo()
734        .first()
735        .
736}
737            "#,
738        );
739        type_char_noop(
740            '.',
741            r#"
742fn main() {
743    xs.foo()
744        .first()
745        $0
746}
747            "#,
748        );
749    }
750
751    #[test]
752    fn indents_middle_of_chain_call() {
753        type_char(
754            '.',
755            r#"
756fn source_impl() {
757    let var = enum_defvariant_list().unwrap()
758    $0
759        .nth(92)
760        .unwrap();
761}
762            "#,
763            r#"
764fn source_impl() {
765    let var = enum_defvariant_list().unwrap()
766        .
767        .nth(92)
768        .unwrap();
769}
770            "#,
771        );
772        type_char_noop(
773            '.',
774            r#"
775fn source_impl() {
776    let var = enum_defvariant_list().unwrap()
777        $0
778        .nth(92)
779        .unwrap();
780}
781            "#,
782        );
783    }
784
785    #[test]
786    fn dont_indent_freestanding_dot() {
787        type_char_noop(
788            '.',
789            r#"
790fn main() {
791    $0
792}
793            "#,
794        );
795        type_char_noop(
796            '.',
797            r#"
798fn main() {
799$0
800}
801            "#,
802        );
803    }
804
805    #[test]
806    fn adds_space_after_return_type() {
807        type_char(
808            '>',
809            r#"
810fn foo() -$0{ 92 }
811"#,
812            r#"
813fn foo() -> { 92 }
814"#,
815        );
816    }
817
818    #[test]
819    fn adds_closing_brace_for_expr() {
820        type_char(
821            '{',
822            r#"
823fn f() { match () { _ => $0() } }
824            "#,
825            r#"
826fn f() { match () { _ => {()} } }
827            "#,
828        );
829        type_char(
830            '{',
831            r#"
832fn f() { $0() }
833            "#,
834            r#"
835fn f() { {()} }
836            "#,
837        );
838        type_char(
839            '{',
840            r#"
841fn f() { let x = $0(); }
842            "#,
843            r#"
844fn f() { let x = {()}; }
845            "#,
846        );
847        type_char(
848            '{',
849            r#"
850fn f() { let x = $0a.b(); }
851            "#,
852            r#"
853fn f() { let x = {a.b()}; }
854            "#,
855        );
856        type_char(
857            '{',
858            r#"
859const S: () = $0();
860fn f() {}
861            "#,
862            r#"
863const S: () = {()};
864fn f() {}
865            "#,
866        );
867        type_char(
868            '{',
869            r#"
870const S: () = $0a.b();
871fn f() {}
872            "#,
873            r#"
874const S: () = {a.b()};
875fn f() {}
876            "#,
877        );
878        type_char(
879            '{',
880            r#"
881fn f() {
882    match x {
883        0 => $0(),
884        1 => (),
885    }
886}
887            "#,
888            r#"
889fn f() {
890    match x {
891        0 => {()},
892        1 => (),
893    }
894}
895            "#,
896        );
897        type_char(
898            '{',
899            r#"
900fn main() {
901    #[allow(unreachable_code)]
902    $0g();
903}
904            "#,
905            r#"
906fn main() {
907    #[allow(unreachable_code)]
908    {g()};
909}
910            "#,
911        );
912    }
913
914    #[test]
915    fn noop_in_string_literal() {
916        // Regression test for #9351
917        type_char_noop(
918            '{',
919            r##"
920fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
921    let base = r#"
922enum E { T(), R$0, C }
923use self::E::X;
924const Z: E = E::C;
925mod m {}
926asdasdasdasdasdasda
927sdasdasdasdasdasda
928sdasdasdasdasd
929"#;
930    let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
931    expect.assert_eq(&actual)
932}
933            "##,
934        );
935    }
936
937    #[test]
938    fn noop_in_item_position_with_macro() {
939        type_char_noop('{', r#"$0println!();"#);
940        type_char_noop(
941            '{',
942            r#"
943fn main() $0println!("hello");
944}"#,
945        );
946    }
947
948    #[test]
949    fn adds_closing_brace_for_use_tree() {
950        type_char(
951            '{',
952            r#"
953use some::$0Path;
954            "#,
955            r#"
956use some::{Path};
957            "#,
958        );
959        type_char(
960            '{',
961            r#"
962use some::{Path, $0Other};
963            "#,
964            r#"
965use some::{Path, {Other}};
966            "#,
967        );
968        type_char(
969            '{',
970            r#"
971use some::{$0Path, Other};
972            "#,
973            r#"
974use some::{{Path}, Other};
975            "#,
976        );
977        type_char(
978            '{',
979            r#"
980use some::path::$0to::Item;
981            "#,
982            r#"
983use some::path::{to::Item};
984            "#,
985        );
986        type_char(
987            '{',
988            r#"
989use some::$0path::to::Item;
990            "#,
991            r#"
992use some::{path::to::Item};
993            "#,
994        );
995        type_char(
996            '{',
997            r#"
998use $0some::path::to::Item;
999            "#,
1000            r#"
1001use {some::path::to::Item};
1002            "#,
1003        );
1004        type_char(
1005            '{',
1006            r#"
1007use some::path::$0to::{Item};
1008            "#,
1009            r#"
1010use some::path::{to::{Item}};
1011            "#,
1012        );
1013        type_char(
1014            '{',
1015            r#"
1016use $0Thing as _;
1017            "#,
1018            r#"
1019use {Thing as _};
1020            "#,
1021        );
1022
1023        type_char_noop(
1024            '{',
1025            r#"
1026use some::pa$0th::to::Item;
1027            "#,
1028        );
1029    }
1030
1031    #[test]
1032    fn adds_closing_parenthesis_for_expr() {
1033        type_char(
1034            '(',
1035            r#"
1036fn f() { match () { _ => $0() } }
1037            "#,
1038            r#"
1039fn f() { match () { _ => (()) } }
1040            "#,
1041        );
1042        type_char(
1043            '(',
1044            r#"
1045fn f() { $0() }
1046            "#,
1047            r#"
1048fn f() { (()) }
1049            "#,
1050        );
1051        type_char(
1052            '(',
1053            r#"
1054fn f() { let x = $0(); }
1055            "#,
1056            r#"
1057fn f() { let x = (()); }
1058            "#,
1059        );
1060        type_char(
1061            '(',
1062            r#"
1063fn f() { let x = $0a.b(); }
1064            "#,
1065            r#"
1066fn f() { let x = (a.b()); }
1067            "#,
1068        );
1069        type_char(
1070            '(',
1071            r#"
1072const S: () = $0();
1073fn f() {}
1074            "#,
1075            r#"
1076const S: () = (());
1077fn f() {}
1078            "#,
1079        );
1080        type_char(
1081            '(',
1082            r#"
1083const S: () = $0a.b();
1084fn f() {}
1085            "#,
1086            r#"
1087const S: () = (a.b());
1088fn f() {}
1089            "#,
1090        );
1091        type_char(
1092            '(',
1093            r#"
1094fn f() {
1095    match x {
1096        0 => $0(),
1097        1 => (),
1098    }
1099}
1100            "#,
1101            r#"
1102fn f() {
1103    match x {
1104        0 => (()),
1105        1 => (),
1106    }
1107}
1108            "#,
1109        );
1110        type_char(
1111            '(',
1112            r#"
1113        fn f() {
1114            let z = Some($03);
1115        }
1116                    "#,
1117            r#"
1118        fn f() {
1119            let z = Some((3));
1120        }
1121                    "#,
1122        );
1123    }
1124
1125    #[test]
1126    fn preceding_whitespace_is_significant_for_closing_brackets() {
1127        type_char_noop(
1128            '(',
1129            r#"
1130fn f() { a.b$0if true {} }
1131"#,
1132        );
1133        type_char_noop(
1134            '(',
1135            r#"
1136fn f() { foo$0{} }
1137"#,
1138        );
1139    }
1140
1141    #[test]
1142    fn adds_closing_parenthesis_for_pat() {
1143        type_char(
1144            '(',
1145            r#"
1146fn f() { match () { $0() => () } }
1147"#,
1148            r#"
1149fn f() { match () { (()) => () } }
1150"#,
1151        );
1152        type_char(
1153            '(',
1154            r#"
1155fn f($0n: ()) {}
1156"#,
1157            r#"
1158fn f((n): ()) {}
1159"#,
1160        );
1161    }
1162
1163    #[test]
1164    fn adds_closing_parenthesis_for_ty() {
1165        type_char(
1166            '(',
1167            r#"
1168fn f(n: $0()) {}
1169"#,
1170            r#"
1171fn f(n: (())) {}
1172"#,
1173        );
1174        type_char(
1175            '(',
1176            r#"
1177fn f(n: $0a::b::<d>::c) {}
1178"#,
1179            r#"
1180fn f(n: (a::b::<d>::c)) {}
1181"#,
1182        );
1183    }
1184
1185    #[test]
1186    fn adds_closing_angles_for_ty() {
1187        type_char(
1188            '<',
1189            r#"
1190fn f(n: $0()) {}
1191"#,
1192            r#"
1193fn f(n: <()>) {}
1194"#,
1195        );
1196        type_char(
1197            '<',
1198            r#"
1199fn f(n: $0a::b::<d>::c) {}
1200"#,
1201            r#"
1202fn f(n: <a::b::<d>::c>) {}
1203"#,
1204        );
1205        type_char(
1206            '<',
1207            r#"
1208fn f(n: a$0b::<d>::c) {}
1209"#,
1210            r#"
1211fn f(n: a<>b::<d>::c) {}
1212"#,
1213        );
1214    }
1215
1216    #[test]
1217    fn parenthesis_noop_in_string_literal() {
1218        // Regression test for #9351
1219        type_char_noop(
1220            '(',
1221            r##"
1222fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
1223    let base = r#"
1224enum E { T(), R$0, C }
1225use self::E::X;
1226const Z: E = E::C;
1227mod m {}
1228asdasdasdasdasdasda
1229sdasdasdasdasdasda
1230sdasdasdasdasd
1231"#;
1232    let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
1233    expect.assert_eq(&actual)
1234}
1235            "##,
1236        );
1237    }
1238
1239    #[test]
1240    fn parenthesis_noop_in_item_position_with_macro() {
1241        type_char_noop('(', r#"$0println!();"#);
1242    }
1243
1244    #[test]
1245    fn parenthesis_noop_in_use_tree() {
1246        type_char_noop(
1247            '(',
1248            r#"
1249use some::$0Path;
1250            "#,
1251        );
1252        type_char_noop(
1253            '(',
1254            r#"
1255use some::{Path, $0Other};
1256            "#,
1257        );
1258        type_char_noop(
1259            '(',
1260            r#"
1261use some::{$0Path, Other};
1262            "#,
1263        );
1264        type_char_noop(
1265            '(',
1266            r#"
1267use some::path::$0to::Item;
1268            "#,
1269        );
1270        type_char_noop(
1271            '(',
1272            r#"
1273use some::$0path::to::Item;
1274            "#,
1275        );
1276        type_char_noop(
1277            '(',
1278            r#"
1279use $0some::path::to::Item;
1280            "#,
1281        );
1282        type_char_noop(
1283            '(',
1284            r#"
1285use some::path::$0to::{Item};
1286            "#,
1287        );
1288        type_char_noop(
1289            '(',
1290            r#"
1291use $0Thing as _;
1292            "#,
1293        );
1294
1295        type_char_noop(
1296            '(',
1297            r#"
1298use some::pa$0th::to::Item;
1299            "#,
1300        );
1301        type_char_noop(
1302            '<',
1303            r#"
1304use some::pa$0th::to::Item;
1305            "#,
1306        );
1307    }
1308
1309    #[test]
1310    fn adds_closing_angle_bracket_for_generic_args() {
1311        type_char(
1312            '<',
1313            r#"
1314fn foo() {
1315    bar::$0
1316}
1317            "#,
1318            r#"
1319fn foo() {
1320    bar::<>
1321}
1322            "#,
1323        );
1324
1325        type_char(
1326            '<',
1327            r#"
1328fn foo(bar: &[u64]) {
1329    bar.iter().collect::$0();
1330}
1331            "#,
1332            r#"
1333fn foo(bar: &[u64]) {
1334    bar.iter().collect::<>();
1335}
1336            "#,
1337        );
1338    }
1339
1340    #[test]
1341    fn adds_closing_angle_bracket_for_generic_params() {
1342        type_char(
1343            '<',
1344            r#"
1345fn foo$0() {}
1346            "#,
1347            r#"
1348fn foo<>() {}
1349            "#,
1350        );
1351        type_char(
1352            '<',
1353            r#"
1354fn foo$0
1355            "#,
1356            r#"
1357fn foo<>
1358            "#,
1359        );
1360        type_char(
1361            '<',
1362            r#"
1363struct Foo$0 {}
1364            "#,
1365            r#"
1366struct Foo<> {}
1367            "#,
1368        );
1369        type_char(
1370            '<',
1371            r#"
1372struct Foo$0();
1373            "#,
1374            r#"
1375struct Foo<>();
1376            "#,
1377        );
1378        type_char(
1379            '<',
1380            r#"
1381struct Foo$0
1382            "#,
1383            r#"
1384struct Foo<>
1385            "#,
1386        );
1387        type_char(
1388            '<',
1389            r#"
1390enum Foo$0
1391            "#,
1392            r#"
1393enum Foo<>
1394            "#,
1395        );
1396        type_char(
1397            '<',
1398            r#"
1399trait Foo$0
1400            "#,
1401            r#"
1402trait Foo<>
1403            "#,
1404        );
1405        type_char(
1406            '<',
1407            r#"
1408type Foo$0 = Bar;
1409            "#,
1410            r#"
1411type Foo<> = Bar;
1412            "#,
1413        );
1414        type_char(
1415            '<',
1416            r#"
1417impl<T> Foo$0 {}
1418            "#,
1419            r#"
1420impl<T> Foo<> {}
1421            "#,
1422        );
1423        type_char(
1424            '<',
1425            r#"
1426impl Foo$0 {}
1427            "#,
1428            r#"
1429impl Foo<> {}
1430            "#,
1431        );
1432    }
1433
1434    #[test]
1435    fn dont_add_closing_angle_bracket_for_comparison() {
1436        type_char_noop(
1437            '<',
1438            r#"
1439fn main() {
1440    42$0
1441}
1442            "#,
1443        );
1444        type_char_noop(
1445            '<',
1446            r#"
1447fn main() {
1448    42 $0
1449}
1450            "#,
1451        );
1452        type_char_noop(
1453            '<',
1454            r#"
1455fn main() {
1456    let foo = 42;
1457    foo $0
1458}
1459            "#,
1460        );
1461    }
1462
1463    #[test]
1464    fn dont_add_closing_angle_bracket_if_it_is_already_there() {
1465        type_char_noop(
1466            '<',
1467            r#"
1468fn foo() {
1469    bar::$0>
1470}
1471            "#,
1472        );
1473        type_char_noop(
1474            '<',
1475            r#"
1476fn foo(bar: &[u64]) {
1477    bar.iter().collect::$0   >();
1478}
1479            "#,
1480        );
1481        type_char_noop(
1482            '<',
1483            r#"
1484fn foo$0>() {}
1485            "#,
1486        );
1487        type_char_noop(
1488            '<',
1489            r#"
1490fn foo$0>
1491            "#,
1492        );
1493        type_char_noop(
1494            '<',
1495            r#"
1496struct Foo$0> {}
1497            "#,
1498        );
1499        type_char_noop(
1500            '<',
1501            r#"
1502struct Foo$0>();
1503            "#,
1504        );
1505        type_char_noop(
1506            '<',
1507            r#"
1508struct Foo$0>
1509            "#,
1510        );
1511        type_char_noop(
1512            '<',
1513            r#"
1514enum Foo$0>
1515            "#,
1516        );
1517        type_char_noop(
1518            '<',
1519            r#"
1520trait Foo$0>
1521            "#,
1522        );
1523        type_char_noop(
1524            '<',
1525            r#"
1526type Foo$0> = Bar;
1527            "#,
1528        );
1529        type_char_noop(
1530            '<',
1531            r#"
1532impl$0> Foo {}
1533            "#,
1534        );
1535        type_char_noop(
1536            '<',
1537            r#"
1538impl<T> Foo$0> {}
1539            "#,
1540        );
1541        type_char_noop(
1542            '<',
1543            r#"
1544impl Foo$0> {}
1545            "#,
1546        );
1547    }
1548
1549    #[test]
1550    fn regression_629() {
1551        type_char_noop(
1552            '.',
1553            r#"
1554fn foo() {
1555    CompletionItem::new(
1556        CompletionKind::Reference,
1557        ctx.source_range(),
1558        field.name().to_string(),
1559    )
1560    .foo()
1561    $0
1562}
1563"#,
1564        );
1565        type_char_noop(
1566            '.',
1567            r#"
1568fn foo() {
1569    CompletionItem::new(
1570        CompletionKind::Reference,
1571        ctx.source_range(),
1572        field.name().to_string(),
1573    )
1574    $0
1575}
1576"#,
1577        );
1578    }
1579
1580    #[test]
1581    fn completes_pipe_param_list() {
1582        type_char(
1583            '|',
1584            r#"
1585fn foo() {
1586    $0
1587}
1588"#,
1589            r#"
1590fn foo() {
1591    ||
1592}
1593"#,
1594        );
1595        type_char(
1596            '|',
1597            r#"
1598fn foo() {
1599    $0 a
1600}
1601"#,
1602            r#"
1603fn foo() {
1604    || a
1605}
1606"#,
1607        );
1608        type_char_noop(
1609            '|',
1610            r#"
1611fn foo() {
1612    let $0
1613}
1614"#,
1615        );
1616    }
1617
1618    #[test]
1619    fn adds_parentheses_around_trait_object_in_ref_type() {
1620        type_char(
1621            '+',
1622            r#"
1623fn foo(x: &dyn A$0) {}
1624"#,
1625            r#"
1626fn foo(x: &(dyn A+)) {}
1627"#,
1628        );
1629        type_char(
1630            '+',
1631            r#"
1632fn foo(x: &'static dyn A$0B) {}
1633"#,
1634            r#"
1635fn foo(x: &'static (dyn A+B)) {}
1636"#,
1637        );
1638        type_char_noop(
1639            '+',
1640            r#"
1641fn foo(x: &(dyn A$0)) {}
1642"#,
1643        );
1644        type_char_noop(
1645            '+',
1646            r#"
1647fn foo(x: Box<dyn A$0>) {}
1648"#,
1649        );
1650    }
1651
1652    #[test]
1653    fn adds_parentheses_around_trait_object_in_ptr_type() {
1654        type_char(
1655            '+',
1656            r#"
1657fn foo(x: *const dyn A$0) {}
1658"#,
1659            r#"
1660fn foo(x: *const (dyn A+)) {}
1661"#,
1662        );
1663    }
1664
1665    #[test]
1666    fn adds_parentheses_around_trait_object_in_return_type() {
1667        type_char(
1668            '+',
1669            r#"
1670fn foo(x: fn() -> dyn A$0) {}
1671"#,
1672            r#"
1673fn foo(x: fn() -> (dyn A+)) {}
1674"#,
1675        );
1676    }
1677}