aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/assist_context.rs7
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs152
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs2
-rw-r--r--crates/assists/src/handlers/auto_import.rs5
-rw-r--r--crates/assists/src/handlers/change_return_type_to_result.rs121
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs213
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs2
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs266
-rw-r--r--crates/assists/src/handlers/flip_comma.rs2
-rw-r--r--crates/assists/src/handlers/flip_trait_bound.rs2
-rw-r--r--crates/assists/src/handlers/infer_function_return_type.rs337
-rw-r--r--crates/assists/src/handlers/introduce_named_lifetime.rs2
-rw-r--r--crates/assists/src/handlers/invert_if.rs2
-rw-r--r--crates/assists/src/handlers/raw_string.rs34
-rw-r--r--crates/assists/src/handlers/remove_mut.rs2
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs7
-rw-r--r--crates/assists/src/handlers/replace_string_with_char.rs8
-rw-r--r--crates/assists/src/handlers/split_import.rs2
-rw-r--r--crates/assists/src/handlers/unwrap_block.rs2
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/assists/src/utils/insert_use.rs31
-rw-r--r--crates/hir_def/src/body/lower.rs21
-rw-r--r--crates/hir_def/src/nameres/collector.rs25
-rw-r--r--crates/hir_def/src/nameres/mod_resolution.rs23
-rw-r--r--crates/hir_def/src/nameres/tests.rs5
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs44
-rw-r--r--crates/hir_expand/src/builtin_macro.rs2
-rw-r--r--crates/ide/src/diagnostics/fixes.rs3
-rw-r--r--crates/ide/src/extend_selection.rs2
-rw-r--r--crates/ide/src/inlay_hints.rs25
-rw-r--r--crates/ide/src/syntax_highlighting.rs16
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs3
-rw-r--r--crates/ide/src/syntax_tree.rs6
-rw-r--r--crates/parser/src/grammar.rs5
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs14
-rw-r--r--crates/parser/src/grammar/items.rs4
-rw-r--r--crates/parser/src/syntax_kind/generated.rs5
-rw-r--r--crates/project_model/src/sysroot.rs8
-rw-r--r--crates/syntax/src/ast/expr_ext.rs55
-rw-r--r--crates/syntax/src/ast/generated/tokens.rs50
-rw-r--r--crates/syntax/src/ast/make.rs64
-rw-r--r--crates/syntax/src/ast/node_ext.rs10
-rw-r--r--crates/syntax/src/ast/token_ext.rs173
-rw-r--r--crates/syntax/src/parsing/lexer.rs114
-rw-r--r--crates/syntax/src/parsing/reparsing.rs2
-rw-r--r--crates/syntax/src/validation.rs52
-rw-r--r--crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt2
-rw-r--r--crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt2
-rw-r--r--crates/syntax/test_data/lexer/ok/0008_byte_strings.txt4
-rw-r--r--crates/syntax/test_data/lexer/ok/0009_strings.txt2
-rw-r--r--crates/syntax/test_data/lexer/ok/0013_raw_strings.txt2
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast4
71 files changed, 1368 insertions, 631 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index d11fee196..fcfe2d6ee 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -12,7 +12,7 @@ use ide_db::{
12}; 12};
13use syntax::{ 13use syntax::{
14 algo::{self, find_node_at_offset, SyntaxRewriter}, 14 algo::{self, find_node_at_offset, SyntaxRewriter},
15 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize, 15 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
16 TokenAtOffset, 16 TokenAtOffset,
17}; 17};
18use text_edit::{TextEdit, TextEditBuilder}; 18use text_edit::{TextEdit, TextEditBuilder};
@@ -81,9 +81,12 @@ impl<'a> AssistContext<'a> {
81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { 81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
82 self.source_file.syntax().token_at_offset(self.offset()) 82 self.source_file.syntax().token_at_offset(self.offset())
83 } 83 }
84 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { 84 pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
85 self.token_at_offset().find(|it| it.kind() == kind) 85 self.token_at_offset().find(|it| it.kind() == kind)
86 } 86 }
87 pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
88 self.token_at_offset().find_map(T::cast)
89 }
87 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { 90 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
88 find_node_at_offset(self.source_file.syntax(), self.offset()) 91 find_node_at_offset(self.source_file.syntax(), self.offset())
89 } 92 }
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 8757fa33f..669dd9b21 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,13 +1,16 @@
1use ide_db::imports_locator;
1use itertools::Itertools; 2use itertools::Itertools;
2use syntax::{ 3use syntax::{
3 ast::{self, AstNode}, 4 ast::{self, make, AstNode},
4 Direction, SmolStr, 5 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE}, 6 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize, 7 TextRange, TextSize,
7}; 8};
8 9
9use crate::{ 10use crate::{
10 assist_context::{AssistContext, Assists}, 11 assist_config::SnippetCap,
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::mod_path_to_ast,
11 AssistId, AssistKind, 14 AssistId, AssistKind,
12}; 15};
13 16
@@ -30,72 +33,116 @@ use crate::{
30// ``` 33// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 35 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34 36
35 let attr_name = attr 37 let attr_name = attr
36 .syntax() 38 .syntax()
37 .descendants_with_tokens() 39 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT) 40 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token()) 41 .find_map(syntax::NodeOrToken::into_token)
40 .filter(|t| *t.text() == "derive")? 42 .filter(|t| t.text() == "derive")?
41 .text() 43 .text()
42 .clone(); 44 .clone();
43 45
44 let trait_token = 46 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; 47 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
48 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
46 49
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 50 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string(); 51 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end(); 52 let insert_pos = annotated.syntax().parent()?.text_range().end();
53
54 let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55 let current_crate = current_module.krate();
56
57 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
58 .into_iter()
59 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
60 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
61 _ => None,
62 })
63 .flat_map(|trait_| {
64 current_module
65 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
66 .as_ref()
67 .map(mod_path_to_ast)
68 .zip(Some(trait_))
69 });
50 70
51 let label = 71 let mut no_traits_found = true;
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 72 for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
74 }
75 if no_traits_found {
76 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77 }
78 Some(())
79}
53 80
81fn add_assist(
82 acc: &mut Assists,
83 snippet_cap: Option<SnippetCap>,
84 attr: &ast::Attr,
85 trait_path: &ast::Path,
86 annotated_name: &str,
87 insert_pos: TextSize,
88) -> Option<()> {
54 let target = attr.syntax().text_range(); 89 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { 90 let input = attr.token_tree()?;
56 let new_attr_input = input 91 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
57 .syntax() 92 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64
65 if has_more_derives {
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80 93
81 match ctx.config.snippet_cap { 94 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
95 update_attribute(builder, &input, &trait_name, &attr);
96 match snippet_cap {
82 Some(cap) => { 97 Some(cap) => {
83 builder.insert_snippet( 98 builder.insert_snippet(
84 cap, 99 cap,
85 start_offset, 100 insert_pos,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), 101 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
87 ); 102 );
88 } 103 }
89 None => { 104 None => {
90 builder.insert( 105 builder.insert(
91 start_offset, 106 insert_pos,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), 107 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
93 ); 108 );
94 } 109 }
95 } 110 }
96 }) 111 })
97} 112}
98 113
114fn update_attribute(
115 builder: &mut AssistBuilder,
116 input: &ast::TokenTree,
117 trait_name: &ast::NameRef,
118 attr: &ast::Attr,
119) {
120 let new_attr_input = input
121 .syntax()
122 .descendants_with_tokens()
123 .filter(|t| t.kind() == IDENT)
124 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
125 .filter(|t| t != trait_name.text())
126 .collect::<Vec<SmolStr>>();
127 let has_more_derives = !new_attr_input.is_empty();
128
129 if has_more_derives {
130 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
131 builder.replace(input.syntax().text_range(), new_attr_input);
132 } else {
133 let attr_range = attr.syntax().text_range();
134 builder.delete(attr_range);
135
136 let line_break_range = attr
137 .syntax()
138 .next_sibling_or_token()
139 .filter(|t| t.kind() == WHITESPACE)
140 .map(|t| t.text_range())
141 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142 builder.delete(line_break_range);
143 }
144}
145
99#[cfg(test)] 146#[cfg(test)]
100mod tests { 147mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable}; 148 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -103,6 +150,35 @@ mod tests {
103 use super::*; 150 use super::*;
104 151
105 #[test] 152 #[test]
153 fn add_custom_impl_qualified() {
154 check_assist(
155 add_custom_impl,
156 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl fmt::Debug for Foo {
176 $0
177}
178",
179 )
180 }
181 #[test]
106 fn add_custom_impl_for_unique_input() { 182 fn add_custom_impl_for_unique_input() {
107 check_assist( 183 check_assist(
108 add_custom_impl, 184 add_custom_impl,
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs
index e3d84d698..1f486c013 100644
--- a/crates/assists/src/handlers/add_turbo_fish.rs
+++ b/crates/assists/src/handlers/add_turbo_fish.rs
@@ -25,7 +25,7 @@ use crate::{
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| { 28 let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?; 29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30 if arg_list.args().count() > 0 { 30 if arg_list.args().count() > 0 {
31 return None; 31 return None;
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index e49e641b3..37dd61266 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -99,7 +99,6 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
100 let group = import_group_message(import_assets.import_candidate()); 100 let group = import_group_message(import_assets.import_candidate());
101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
102 let syntax = scope.as_syntax_node();
103 for (import, _) in proposed_imports { 102 for (import, _) in proposed_imports {
104 acc.add_group( 103 acc.add_group(
105 &group, 104 &group,
@@ -107,9 +106,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
107 format!("Import `{}`", &import), 106 format!("Import `{}`", &import),
108 range, 107 range,
109 |builder| { 108 |builder| {
110 let new_syntax = 109 let rewriter =
111 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge); 110 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
112 builder.replace(syntax.text_range(), new_syntax.to_string()) 111 builder.rewrite(rewriter);
113 }, 112 },
114 ); 113 );
115 } 114 }
diff --git a/crates/assists/src/handlers/change_return_type_to_result.rs b/crates/assists/src/handlers/change_return_type_to_result.rs
index be480943c..76f33a5b6 100644
--- a/crates/assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/assists/src/handlers/change_return_type_to_result.rs
@@ -2,7 +2,7 @@ use std::iter;
2 2
3use syntax::{ 3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner}, 4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 AstNode, SyntaxNode, 5 match_ast, AstNode, SyntaxNode,
6}; 6};
7use test_utils::mark; 7use test_utils::mark;
8 8
@@ -21,8 +21,18 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
21// ``` 21// ```
22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; 23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 // FIXME: extend to lambdas as well 24 let parent = ret_type.syntax().parent()?;
25 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?; 25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
26 36
27 let type_ref = &ret_type.ty()?; 37 let type_ref = &ret_type.ty()?;
28 let ret_type_str = type_ref.syntax().text().to_string(); 38 let ret_type_str = type_ref.syntax().text().to_string();
@@ -34,16 +44,14 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
34 } 44 }
35 } 45 }
36 46
37 let block_expr = &fn_def.body()?;
38
39 acc.add( 47 acc.add(
40 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite), 48 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
41 "Wrap return type in Result", 49 "Wrap return type in Result",
42 type_ref.syntax().text_range(), 50 type_ref.syntax().text_range(),
43 |builder| { 51 |builder| {
44 let mut tail_return_expr_collector = TailReturnCollector::new(); 52 let mut tail_return_expr_collector = TailReturnCollector::new();
45 tail_return_expr_collector.collect_jump_exprs(block_expr, false); 53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
46 tail_return_expr_collector.collect_tail_exprs(block_expr); 54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
47 55
48 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { 56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
49 let ok_wrapped = make::expr_call( 57 let ok_wrapped = make::expr_call(
@@ -285,16 +293,20 @@ mod tests {
285 } 293 }
286 294
287 #[test] 295 #[test]
288 fn change_return_type_to_result_simple_return_type() { 296 fn change_return_type_to_result_simple_closure() {
289 check_assist( 297 check_assist(
290 change_return_type_to_result, 298 change_return_type_to_result,
291 r#"fn foo() -> i32<|> { 299 r#"fn foo() {
292 let test = "test"; 300 || -> i32<|> {
293 return 42i32; 301 let test = "test";
302 return 42i32;
303 };
294 }"#, 304 }"#,
295 r#"fn foo() -> Result<i32, ${0:_}> { 305 r#"fn foo() {
296 let test = "test"; 306 || -> Result<i32, ${0:_}> {
297 return Ok(42i32); 307 let test = "test";
308 return Ok(42i32);
309 };
298 }"#, 310 }"#,
299 ); 311 );
300 } 312 }
@@ -311,6 +323,29 @@ mod tests {
311 } 323 }
312 324
313 #[test] 325 #[test]
326 fn change_return_type_to_result_simple_return_type_bad_cursor_closure() {
327 check_assist_not_applicable(
328 change_return_type_to_result,
329 r#"fn foo() {
330 || -> i32 {
331 let test = "test";<|>
332 return 42i32;
333 };
334 }"#,
335 );
336 }
337
338 #[test]
339 fn change_return_type_to_result_closure_non_block() {
340 check_assist_not_applicable(
341 change_return_type_to_result,
342 r#"fn foo() {
343 || -> i<|>32 3;
344 }"#,
345 );
346 }
347
348 #[test]
314 fn change_return_type_to_result_simple_return_type_already_result_std() { 349 fn change_return_type_to_result_simple_return_type_already_result_std() {
315 check_assist_not_applicable( 350 check_assist_not_applicable(
316 change_return_type_to_result, 351 change_return_type_to_result,
@@ -334,6 +369,19 @@ mod tests {
334 } 369 }
335 370
336 #[test] 371 #[test]
372 fn change_return_type_to_result_simple_return_type_already_result_closure() {
373 check_assist_not_applicable(
374 change_return_type_to_result,
375 r#"fn foo() {
376 || -> Result<i32<|>, String> {
377 let test = "test";
378 return 42i32;
379 };
380 }"#,
381 );
382 }
383
384 #[test]
337 fn change_return_type_to_result_simple_with_cursor() { 385 fn change_return_type_to_result_simple_with_cursor() {
338 check_assist( 386 check_assist(
339 change_return_type_to_result, 387 change_return_type_to_result,
@@ -364,6 +412,25 @@ mod tests {
364 } 412 }
365 413
366 #[test] 414 #[test]
415 fn change_return_type_to_result_simple_with_tail_closure() {
416 check_assist(
417 change_return_type_to_result,
418 r#"fn foo() {
419 || -><|> i32 {
420 let test = "test";
421 42i32
422 };
423 }"#,
424 r#"fn foo() {
425 || -> Result<i32, ${0:_}> {
426 let test = "test";
427 Ok(42i32)
428 };
429 }"#,
430 );
431 }
432
433 #[test]
367 fn change_return_type_to_result_simple_with_tail_only() { 434 fn change_return_type_to_result_simple_with_tail_only() {
368 check_assist( 435 check_assist(
369 change_return_type_to_result, 436 change_return_type_to_result,
@@ -375,6 +442,7 @@ mod tests {
375 }"#, 442 }"#,
376 ); 443 );
377 } 444 }
445
378 #[test] 446 #[test]
379 fn change_return_type_to_result_simple_with_tail_block_like() { 447 fn change_return_type_to_result_simple_with_tail_block_like() {
380 check_assist( 448 check_assist(
@@ -397,6 +465,31 @@ mod tests {
397 } 465 }
398 466
399 #[test] 467 #[test]
468 fn change_return_type_to_result_simple_without_block_closure() {
469 check_assist(
470 change_return_type_to_result,
471 r#"fn foo() {
472 || -> i32<|> {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478 };
479 }"#,
480 r#"fn foo() {
481 || -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487 };
488 }"#,
489 );
490 }
491
492 #[test]
400 fn change_return_type_to_result_simple_with_nested_if() { 493 fn change_return_type_to_result_simple_with_nested_if() {
401 check_assist( 494 check_assist(
402 change_return_type_to_result, 495 change_return_type_to_result,
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
index ea35e833a..1094ed3f3 100644
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -1,4 +1,4 @@
1use syntax::{ast, AstNode, SmolStr}; 1use syntax::{ast, ast::Radix, AstToken};
2 2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4 4
@@ -15,40 +15,34 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
15// ``` 15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?; 17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let literal = match literal.kind() {
19 ast::LiteralKind::IntNumber(it) => it,
20 _ => return None,
21 };
22 let radix = literal.radix();
23 let value = literal.value()?;
24 let suffix = literal.suffix();
25
18 let range = literal.syntax().text_range(); 26 let range = literal.syntax().text_range();
19 let group_id = GroupLabel("Convert integer base".into()); 27 let group_id = GroupLabel("Convert integer base".into());
20 28
21 let suffix = match literal.kind() { 29 for &target_radix in Radix::ALL {
22 ast::LiteralKind::IntNumber { suffix } => suffix, 30 if target_radix == radix {
23 _ => return None,
24 };
25 let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0);
26 let raw_literal_text = literal.syntax().to_string();
27
28 // Gets the literal's text without the type suffix and without underscores.
29 let literal_text = raw_literal_text
30 .chars()
31 .take(raw_literal_text.len() - suffix_len)
32 .filter(|c| *c != '_')
33 .collect::<SmolStr>();
34 let literal_base = IntegerLiteralBase::identify(&literal_text)?;
35
36 for base in IntegerLiteralBase::bases() {
37 if *base == literal_base {
38 continue; 31 continue;
39 } 32 }
40 33
41 let mut converted = literal_base.convert(&literal_text, base); 34 let mut converted = match target_radix {
42 35 Radix::Binary => format!("0b{:b}", value),
43 let label = if let Some(suffix) = &suffix { 36 Radix::Octal => format!("0o{:o}", value),
44 format!("Convert {} ({}) to {}", &literal_text, suffix, &converted) 37 Radix::Decimal => value.to_string(),
45 } else { 38 Radix::Hexadecimal => format!("0x{:X}", value),
46 format!("Convert {} to {}", &literal_text, &converted)
47 }; 39 };
48 40
41 let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
42
49 // Appends the type suffix back into the new literal if it exists. 43 // Appends the type suffix back into the new literal if it exists.
50 if let Some(suffix) = &suffix { 44 if let Some(suffix) = suffix {
51 converted.push_str(&suffix); 45 converted.push_str(suffix);
52 } 46 }
53 47
54 acc.add_group( 48 acc.add_group(
@@ -63,79 +57,11 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) ->
63 Some(()) 57 Some(())
64} 58}
65 59
66#[derive(Debug, PartialEq, Eq)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl IntegerLiteralBase {
75 fn identify(literal_text: &str) -> Option<Self> {
76 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
77 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
78 return Some(Self::Decimal);
79 }
80
81 let base = match &literal_text[..2] {
82 "0b" => Self::Binary,
83 "0o" => Self::Octal,
84 "0x" => Self::Hexadecimal,
85 _ => Self::Decimal,
86 };
87
88 // Checks that all characters after the base prefix are all valid digits for that base.
89 if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) {
90 Some(base)
91 } else {
92 None
93 }
94 }
95
96 fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
97 let digits = &literal_text[self.prefix_len()..];
98 let value = u128::from_str_radix(digits, self.base()).unwrap();
99
100 match to {
101 Self::Binary => format!("0b{:b}", value),
102 Self::Octal => format!("0o{:o}", value),
103 Self::Decimal => value.to_string(),
104 Self::Hexadecimal => format!("0x{:X}", value),
105 }
106 }
107
108 const fn base(&self) -> u32 {
109 match self {
110 Self::Binary => 2,
111 Self::Octal => 8,
112 Self::Decimal => 10,
113 Self::Hexadecimal => 16,
114 }
115 }
116
117 const fn prefix_len(&self) -> usize {
118 match self {
119 Self::Decimal => 0,
120 _ => 2,
121 }
122 }
123
124 const fn bases() -> &'static [IntegerLiteralBase] {
125 &[
126 IntegerLiteralBase::Binary,
127 IntegerLiteralBase::Octal,
128 IntegerLiteralBase::Decimal,
129 IntegerLiteralBase::Hexadecimal,
130 ]
131 }
132}
133
134#[cfg(test)] 60#[cfg(test)]
135mod tests { 61mod tests {
62 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
136 63
137 use super::*; 64 use super::*;
138 use crate::tests::{check_assist_by_label, check_assist_target};
139 65
140 #[test] 66 #[test]
141 fn binary_target() { 67 fn binary_target() {
@@ -317,21 +243,21 @@ mod tests {
317 convert_integer_literal, 243 convert_integer_literal,
318 before, 244 before,
319 "const _: i32 = 0b1111101000;", 245 "const _: i32 = 0b1111101000;",
320 "Convert 1000 to 0b1111101000", 246 "Convert 1_00_0 to 0b1111101000",
321 ); 247 );
322 248
323 check_assist_by_label( 249 check_assist_by_label(
324 convert_integer_literal, 250 convert_integer_literal,
325 before, 251 before,
326 "const _: i32 = 0o1750;", 252 "const _: i32 = 0o1750;",
327 "Convert 1000 to 0o1750", 253 "Convert 1_00_0 to 0o1750",
328 ); 254 );
329 255
330 check_assist_by_label( 256 check_assist_by_label(
331 convert_integer_literal, 257 convert_integer_literal,
332 before, 258 before,
333 "const _: i32 = 0x3E8;", 259 "const _: i32 = 0x3E8;",
334 "Convert 1000 to 0x3E8", 260 "Convert 1_00_0 to 0x3E8",
335 ); 261 );
336 } 262 }
337 263
@@ -343,21 +269,21 @@ mod tests {
343 convert_integer_literal, 269 convert_integer_literal,
344 before, 270 before,
345 "const _: i32 = 0b1010;", 271 "const _: i32 = 0b1010;",
346 "Convert 10 to 0b1010", 272 "Convert 1_0 to 0b1010",
347 ); 273 );
348 274
349 check_assist_by_label( 275 check_assist_by_label(
350 convert_integer_literal, 276 convert_integer_literal,
351 before, 277 before,
352 "const _: i32 = 0o12;", 278 "const _: i32 = 0o12;",
353 "Convert 10 to 0o12", 279 "Convert 1_0 to 0o12",
354 ); 280 );
355 281
356 check_assist_by_label( 282 check_assist_by_label(
357 convert_integer_literal, 283 convert_integer_literal,
358 before, 284 before,
359 "const _: i32 = 0xA;", 285 "const _: i32 = 0xA;",
360 "Convert 10 to 0xA", 286 "Convert 1_0 to 0xA",
361 ); 287 );
362 } 288 }
363 289
@@ -369,21 +295,21 @@ mod tests {
369 convert_integer_literal, 295 convert_integer_literal,
370 before, 296 before,
371 "const _: i32 = 0b11111111;", 297 "const _: i32 = 0b11111111;",
372 "Convert 0xFF to 0b11111111", 298 "Convert 0x_F_F to 0b11111111",
373 ); 299 );
374 300
375 check_assist_by_label( 301 check_assist_by_label(
376 convert_integer_literal, 302 convert_integer_literal,
377 before, 303 before,
378 "const _: i32 = 0o377;", 304 "const _: i32 = 0o377;",
379 "Convert 0xFF to 0o377", 305 "Convert 0x_F_F to 0o377",
380 ); 306 );
381 307
382 check_assist_by_label( 308 check_assist_by_label(
383 convert_integer_literal, 309 convert_integer_literal,
384 before, 310 before,
385 "const _: i32 = 255;", 311 "const _: i32 = 255;",
386 "Convert 0xFF to 255", 312 "Convert 0x_F_F to 255",
387 ); 313 );
388 } 314 }
389 315
@@ -395,21 +321,21 @@ mod tests {
395 convert_integer_literal, 321 convert_integer_literal,
396 before, 322 before,
397 "const _: i32 = 0o377;", 323 "const _: i32 = 0o377;",
398 "Convert 0b11111111 to 0o377", 324 "Convert 0b1111_1111 to 0o377",
399 ); 325 );
400 326
401 check_assist_by_label( 327 check_assist_by_label(
402 convert_integer_literal, 328 convert_integer_literal,
403 before, 329 before,
404 "const _: i32 = 255;", 330 "const _: i32 = 255;",
405 "Convert 0b11111111 to 255", 331 "Convert 0b1111_1111 to 255",
406 ); 332 );
407 333
408 check_assist_by_label( 334 check_assist_by_label(
409 convert_integer_literal, 335 convert_integer_literal,
410 before, 336 before,
411 "const _: i32 = 0xFF;", 337 "const _: i32 = 0xFF;",
412 "Convert 0b11111111 to 0xFF", 338 "Convert 0b1111_1111 to 0xFF",
413 ); 339 );
414 } 340 }
415 341
@@ -421,21 +347,21 @@ mod tests {
421 convert_integer_literal, 347 convert_integer_literal,
422 before, 348 before,
423 "const _: i32 = 0b11111111;", 349 "const _: i32 = 0b11111111;",
424 "Convert 0o377 to 0b11111111", 350 "Convert 0o3_77 to 0b11111111",
425 ); 351 );
426 352
427 check_assist_by_label( 353 check_assist_by_label(
428 convert_integer_literal, 354 convert_integer_literal,
429 before, 355 before,
430 "const _: i32 = 255;", 356 "const _: i32 = 255;",
431 "Convert 0o377 to 255", 357 "Convert 0o3_77 to 255",
432 ); 358 );
433 359
434 check_assist_by_label( 360 check_assist_by_label(
435 convert_integer_literal, 361 convert_integer_literal,
436 before, 362 before,
437 "const _: i32 = 0xFF;", 363 "const _: i32 = 0xFF;",
438 "Convert 0o377 to 0xFF", 364 "Convert 0o3_77 to 0xFF",
439 ); 365 );
440 } 366 }
441 367
@@ -447,21 +373,21 @@ mod tests {
447 convert_integer_literal, 373 convert_integer_literal,
448 before, 374 before,
449 "const _: i32 = 0b1111101000i32;", 375 "const _: i32 = 0b1111101000i32;",
450 "Convert 1000 (i32) to 0b1111101000", 376 "Convert 1000i32 to 0b1111101000i32",
451 ); 377 );
452 378
453 check_assist_by_label( 379 check_assist_by_label(
454 convert_integer_literal, 380 convert_integer_literal,
455 before, 381 before,
456 "const _: i32 = 0o1750i32;", 382 "const _: i32 = 0o1750i32;",
457 "Convert 1000 (i32) to 0o1750", 383 "Convert 1000i32 to 0o1750i32",
458 ); 384 );
459 385
460 check_assist_by_label( 386 check_assist_by_label(
461 convert_integer_literal, 387 convert_integer_literal,
462 before, 388 before,
463 "const _: i32 = 0x3E8i32;", 389 "const _: i32 = 0x3E8i32;",
464 "Convert 1000 (i32) to 0x3E8", 390 "Convert 1000i32 to 0x3E8i32",
465 ); 391 );
466 } 392 }
467 393
@@ -473,21 +399,21 @@ mod tests {
473 convert_integer_literal, 399 convert_integer_literal,
474 before, 400 before,
475 "const _: i32 = 0b1010i32;", 401 "const _: i32 = 0b1010i32;",
476 "Convert 10 (i32) to 0b1010", 402 "Convert 10i32 to 0b1010i32",
477 ); 403 );
478 404
479 check_assist_by_label( 405 check_assist_by_label(
480 convert_integer_literal, 406 convert_integer_literal,
481 before, 407 before,
482 "const _: i32 = 0o12i32;", 408 "const _: i32 = 0o12i32;",
483 "Convert 10 (i32) to 0o12", 409 "Convert 10i32 to 0o12i32",
484 ); 410 );
485 411
486 check_assist_by_label( 412 check_assist_by_label(
487 convert_integer_literal, 413 convert_integer_literal,
488 before, 414 before,
489 "const _: i32 = 0xAi32;", 415 "const _: i32 = 0xAi32;",
490 "Convert 10 (i32) to 0xA", 416 "Convert 10i32 to 0xAi32",
491 ); 417 );
492 } 418 }
493 419
@@ -499,21 +425,21 @@ mod tests {
499 convert_integer_literal, 425 convert_integer_literal,
500 before, 426 before,
501 "const _: i32 = 0b11111111i32;", 427 "const _: i32 = 0b11111111i32;",
502 "Convert 0xFF (i32) to 0b11111111", 428 "Convert 0xFFi32 to 0b11111111i32",
503 ); 429 );
504 430
505 check_assist_by_label( 431 check_assist_by_label(
506 convert_integer_literal, 432 convert_integer_literal,
507 before, 433 before,
508 "const _: i32 = 0o377i32;", 434 "const _: i32 = 0o377i32;",
509 "Convert 0xFF (i32) to 0o377", 435 "Convert 0xFFi32 to 0o377i32",
510 ); 436 );
511 437
512 check_assist_by_label( 438 check_assist_by_label(
513 convert_integer_literal, 439 convert_integer_literal,
514 before, 440 before,
515 "const _: i32 = 255i32;", 441 "const _: i32 = 255i32;",
516 "Convert 0xFF (i32) to 255", 442 "Convert 0xFFi32 to 255i32",
517 ); 443 );
518 } 444 }
519 445
@@ -525,21 +451,21 @@ mod tests {
525 convert_integer_literal, 451 convert_integer_literal,
526 before, 452 before,
527 "const _: i32 = 0o377i32;", 453 "const _: i32 = 0o377i32;",
528 "Convert 0b11111111 (i32) to 0o377", 454 "Convert 0b11111111i32 to 0o377i32",
529 ); 455 );
530 456
531 check_assist_by_label( 457 check_assist_by_label(
532 convert_integer_literal, 458 convert_integer_literal,
533 before, 459 before,
534 "const _: i32 = 255i32;", 460 "const _: i32 = 255i32;",
535 "Convert 0b11111111 (i32) to 255", 461 "Convert 0b11111111i32 to 255i32",
536 ); 462 );
537 463
538 check_assist_by_label( 464 check_assist_by_label(
539 convert_integer_literal, 465 convert_integer_literal,
540 before, 466 before,
541 "const _: i32 = 0xFFi32;", 467 "const _: i32 = 0xFFi32;",
542 "Convert 0b11111111 (i32) to 0xFF", 468 "Convert 0b11111111i32 to 0xFFi32",
543 ); 469 );
544 } 470 }
545 471
@@ -551,21 +477,21 @@ mod tests {
551 convert_integer_literal, 477 convert_integer_literal,
552 before, 478 before,
553 "const _: i32 = 0b11111111i32;", 479 "const _: i32 = 0b11111111i32;",
554 "Convert 0o377 (i32) to 0b11111111", 480 "Convert 0o377i32 to 0b11111111i32",
555 ); 481 );
556 482
557 check_assist_by_label( 483 check_assist_by_label(
558 convert_integer_literal, 484 convert_integer_literal,
559 before, 485 before,
560 "const _: i32 = 255i32;", 486 "const _: i32 = 255i32;",
561 "Convert 0o377 (i32) to 255", 487 "Convert 0o377i32 to 255i32",
562 ); 488 );
563 489
564 check_assist_by_label( 490 check_assist_by_label(
565 convert_integer_literal, 491 convert_integer_literal,
566 before, 492 before,
567 "const _: i32 = 0xFFi32;", 493 "const _: i32 = 0xFFi32;",
568 "Convert 0o377 (i32) to 0xFF", 494 "Convert 0o377i32 to 0xFFi32",
569 ); 495 );
570 } 496 }
571 497
@@ -577,21 +503,21 @@ mod tests {
577 convert_integer_literal, 503 convert_integer_literal,
578 before, 504 before,
579 "const _: i32 = 0b1111101000i32;", 505 "const _: i32 = 0b1111101000i32;",
580 "Convert 1000 (i32) to 0b1111101000", 506 "Convert 1_00_0i32 to 0b1111101000i32",
581 ); 507 );
582 508
583 check_assist_by_label( 509 check_assist_by_label(
584 convert_integer_literal, 510 convert_integer_literal,
585 before, 511 before,
586 "const _: i32 = 0o1750i32;", 512 "const _: i32 = 0o1750i32;",
587 "Convert 1000 (i32) to 0o1750", 513 "Convert 1_00_0i32 to 0o1750i32",
588 ); 514 );
589 515
590 check_assist_by_label( 516 check_assist_by_label(
591 convert_integer_literal, 517 convert_integer_literal,
592 before, 518 before,
593 "const _: i32 = 0x3E8i32;", 519 "const _: i32 = 0x3E8i32;",
594 "Convert 1000 (i32) to 0x3E8", 520 "Convert 1_00_0i32 to 0x3E8i32",
595 ); 521 );
596 } 522 }
597 523
@@ -603,21 +529,21 @@ mod tests {
603 convert_integer_literal, 529 convert_integer_literal,
604 before, 530 before,
605 "const _: i32 = 0b1010i32;", 531 "const _: i32 = 0b1010i32;",
606 "Convert 10 (i32) to 0b1010", 532 "Convert 1_0i32 to 0b1010i32",
607 ); 533 );
608 534
609 check_assist_by_label( 535 check_assist_by_label(
610 convert_integer_literal, 536 convert_integer_literal,
611 before, 537 before,
612 "const _: i32 = 0o12i32;", 538 "const _: i32 = 0o12i32;",
613 "Convert 10 (i32) to 0o12", 539 "Convert 1_0i32 to 0o12i32",
614 ); 540 );
615 541
616 check_assist_by_label( 542 check_assist_by_label(
617 convert_integer_literal, 543 convert_integer_literal,
618 before, 544 before,
619 "const _: i32 = 0xAi32;", 545 "const _: i32 = 0xAi32;",
620 "Convert 10 (i32) to 0xA", 546 "Convert 1_0i32 to 0xAi32",
621 ); 547 );
622 } 548 }
623 549
@@ -629,21 +555,21 @@ mod tests {
629 convert_integer_literal, 555 convert_integer_literal,
630 before, 556 before,
631 "const _: i32 = 0b11111111i32;", 557 "const _: i32 = 0b11111111i32;",
632 "Convert 0xFF (i32) to 0b11111111", 558 "Convert 0x_F_Fi32 to 0b11111111i32",
633 ); 559 );
634 560
635 check_assist_by_label( 561 check_assist_by_label(
636 convert_integer_literal, 562 convert_integer_literal,
637 before, 563 before,
638 "const _: i32 = 0o377i32;", 564 "const _: i32 = 0o377i32;",
639 "Convert 0xFF (i32) to 0o377", 565 "Convert 0x_F_Fi32 to 0o377i32",
640 ); 566 );
641 567
642 check_assist_by_label( 568 check_assist_by_label(
643 convert_integer_literal, 569 convert_integer_literal,
644 before, 570 before,
645 "const _: i32 = 255i32;", 571 "const _: i32 = 255i32;",
646 "Convert 0xFF (i32) to 255", 572 "Convert 0x_F_Fi32 to 255i32",
647 ); 573 );
648 } 574 }
649 575
@@ -655,21 +581,21 @@ mod tests {
655 convert_integer_literal, 581 convert_integer_literal,
656 before, 582 before,
657 "const _: i32 = 0o377i32;", 583 "const _: i32 = 0o377i32;",
658 "Convert 0b11111111 (i32) to 0o377", 584 "Convert 0b1111_1111i32 to 0o377i32",
659 ); 585 );
660 586
661 check_assist_by_label( 587 check_assist_by_label(
662 convert_integer_literal, 588 convert_integer_literal,
663 before, 589 before,
664 "const _: i32 = 255i32;", 590 "const _: i32 = 255i32;",
665 "Convert 0b11111111 (i32) to 255", 591 "Convert 0b1111_1111i32 to 255i32",
666 ); 592 );
667 593
668 check_assist_by_label( 594 check_assist_by_label(
669 convert_integer_literal, 595 convert_integer_literal,
670 before, 596 before,
671 "const _: i32 = 0xFFi32;", 597 "const _: i32 = 0xFFi32;",
672 "Convert 0b11111111 (i32) to 0xFF", 598 "Convert 0b1111_1111i32 to 0xFFi32",
673 ); 599 );
674 } 600 }
675 601
@@ -681,21 +607,28 @@ mod tests {
681 convert_integer_literal, 607 convert_integer_literal,
682 before, 608 before,
683 "const _: i32 = 0b11111111i32;", 609 "const _: i32 = 0b11111111i32;",
684 "Convert 0o377 (i32) to 0b11111111", 610 "Convert 0o3_77i32 to 0b11111111i32",
685 ); 611 );
686 612
687 check_assist_by_label( 613 check_assist_by_label(
688 convert_integer_literal, 614 convert_integer_literal,
689 before, 615 before,
690 "const _: i32 = 255i32;", 616 "const _: i32 = 255i32;",
691 "Convert 0o377 (i32) to 255", 617 "Convert 0o3_77i32 to 255i32",
692 ); 618 );
693 619
694 check_assist_by_label( 620 check_assist_by_label(
695 convert_integer_literal, 621 convert_integer_literal,
696 before, 622 before,
697 "const _: i32 = 0xFFi32;", 623 "const _: i32 = 0xFFi32;",
698 "Convert 0o377 (i32) to 0xFF", 624 "Convert 0o3_77i32 to 0xFFi32",
699 ); 625 );
700 } 626 }
627
628 #[test]
629 fn convert_overflowing_literal() {
630 let before = "const _: i32 =
631 111111111111111111111111111111111111111111111111111111111111111111111111<|>;";
632 check_assist_not_applicable(convert_integer_literal, before);
633 }
701} 634}
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 316a58d88..853266395 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -41,7 +41,7 @@ use crate::{
41// fn qux(bar: Bar, baz: Baz) {} 41// fn qux(bar: Bar, baz: Baz) {}
42// ``` 42// ```
43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let star = ctx.find_token_at_offset(T![*])?; 44 let star = ctx.find_token_syntax_at_offset(T![*])?;
45 let (parent, mod_path) = find_parent_and_path(&star)?; 45 let (parent, mod_path) = find_parent_and_path(&star)?;
46 let target_module = match ctx.sema.resolve_path(&mod_path)? { 46 let target_module = match ctx.sema.resolve_path(&mod_path)? {
47 PathResolution::Def(ModuleDef::Module(it)) => it, 47 PathResolution::Def(ModuleDef::Module(it)) => it,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 178718c5e..14209b771 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -1,16 +1,17 @@
1use hir::{EnumVariant, Module, ModuleDef, Name}; 1use std::iter;
2use ide_db::base_db::FileId; 2
3use either::Either;
4use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
3use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
4use itertools::Itertools; 6use rustc_hash::{FxHashMap, FxHashSet};
5use rustc_hash::FxHashSet;
6use syntax::{ 7use syntax::{
7 algo::find_node_at_offset, 8 algo::find_node_at_offset,
8 ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, 9 algo::SyntaxRewriter,
9 SourceFile, TextRange, TextSize, 10 ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
11 SourceFile, SyntaxElement,
10}; 12};
11 13
12use crate::{ 14use crate::{
13 assist_context::AssistBuilder,
14 utils::{insert_use, mod_path_to_ast, ImportScope}, 15 utils::{insert_use, mod_path_to_ast, ImportScope},
15 AssistContext, AssistId, AssistKind, Assists, 16 AssistContext, AssistId, AssistKind, Assists,
16}; 17};
@@ -33,43 +34,39 @@ pub(crate) fn extract_struct_from_enum_variant(
33 ctx: &AssistContext, 34 ctx: &AssistContext,
34) -> Option<()> { 35) -> Option<()> {
35 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 36 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
36 let field_list = match variant.kind() { 37 let field_list = extract_field_list_if_applicable(&variant)?;
37 ast::StructKind::Tuple(field_list) => field_list,
38 _ => return None,
39 };
40 38
41 // skip 1-tuple variants 39 let variant_name = variant.name()?;
42 if field_list.fields().count() == 1 {
43 return None;
44 }
45
46 let variant_name = variant.name()?.to_string();
47 let variant_hir = ctx.sema.to_def(&variant)?; 40 let variant_hir = ctx.sema.to_def(&variant)?;
48 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { 41 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
49 return None; 42 return None;
50 } 43 }
44
51 let enum_ast = variant.parent_enum(); 45 let enum_ast = variant.parent_enum();
52 let visibility = enum_ast.visibility();
53 let enum_hir = ctx.sema.to_def(&enum_ast)?; 46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
54 let variant_hir_name = variant_hir.name(ctx.db());
55 let enum_module_def = ModuleDef::from(enum_hir);
56 let current_module = enum_hir.module(ctx.db());
57 let target = variant.syntax().text_range(); 47 let target = variant.syntax().text_range();
58 acc.add( 48 acc.add(
59 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), 49 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
60 "Extract struct from enum variant", 50 "Extract struct from enum variant",
61 target, 51 target,
62 |builder| { 52 |builder| {
63 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); 53 let variant_hir_name = variant_hir.name(ctx.db());
64 let res = definition.usages(&ctx.sema).all(); 54 let enum_module_def = ModuleDef::from(enum_hir);
65 let start_offset = variant.parent_enum().syntax().text_range().start(); 55 let usages =
56 Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)).usages(&ctx.sema).all();
57
66 let mut visited_modules_set = FxHashSet::default(); 58 let mut visited_modules_set = FxHashSet::default();
59 let current_module = enum_hir.module(ctx.db());
67 visited_modules_set.insert(current_module); 60 visited_modules_set.insert(current_module);
68 for reference in res { 61 let mut rewriters = FxHashMap::default();
62 for reference in usages {
63 let rewriter = rewriters
64 .entry(reference.file_range.file_id)
65 .or_insert_with(SyntaxRewriter::default);
69 let source_file = ctx.sema.parse(reference.file_range.file_id); 66 let source_file = ctx.sema.parse(reference.file_range.file_id);
70 update_reference( 67 update_reference(
71 ctx, 68 ctx,
72 builder, 69 rewriter,
73 reference, 70 reference,
74 &source_file, 71 &source_file,
75 &enum_module_def, 72 &enum_module_def,
@@ -77,34 +74,62 @@ pub(crate) fn extract_struct_from_enum_variant(
77 &mut visited_modules_set, 74 &mut visited_modules_set,
78 ); 75 );
79 } 76 }
77 let mut rewriter =
78 rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
79 for (file_id, rewriter) in rewriters {
80 builder.edit_file(file_id);
81 builder.rewrite(rewriter);
82 }
83 builder.edit_file(ctx.frange.file_id);
84 update_variant(&mut rewriter, &variant);
80 extract_struct_def( 85 extract_struct_def(
81 builder, 86 &mut rewriter,
82 &enum_ast, 87 &enum_ast,
83 &variant_name, 88 variant_name.clone(),
84 &field_list.to_string(), 89 &field_list,
85 start_offset, 90 &variant.parent_enum().syntax().clone().into(),
86 ctx.frange.file_id, 91 enum_ast.visibility(),
87 &visibility,
88 ); 92 );
89 let list_range = field_list.syntax().text_range(); 93 builder.rewrite(rewriter);
90 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
91 }, 94 },
92 ) 95 )
93} 96}
94 97
95fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool { 98fn extract_field_list_if_applicable(
99 variant: &ast::Variant,
100) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
101 match variant.kind() {
102 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
103 Some(Either::Left(field_list))
104 }
105 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
106 Some(Either::Right(field_list))
107 }
108 _ => None,
109 }
110}
111
112fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
96 variant 113 variant
97 .parent_enum(db) 114 .parent_enum(db)
98 .module(db) 115 .module(db)
99 .scope(db, None) 116 .scope(db, None)
100 .into_iter() 117 .into_iter()
101 .any(|(name, _)| name.to_string() == variant_name) 118 .filter(|(_, def)| match def {
119 // only check type-namespace
120 hir::ScopeDef::ModuleDef(def) => matches!(def,
121 ModuleDef::Module(_) | ModuleDef::Adt(_) |
122 ModuleDef::EnumVariant(_) | ModuleDef::Trait(_) |
123 ModuleDef::TypeAlias(_) | ModuleDef::BuiltinType(_)
124 ),
125 _ => false,
126 })
127 .any(|(name, _)| name == variant_name.as_name())
102} 128}
103 129
104#[allow(dead_code)]
105fn insert_import( 130fn insert_import(
106 ctx: &AssistContext, 131 ctx: &AssistContext,
107 builder: &mut AssistBuilder, 132 rewriter: &mut SyntaxRewriter,
108 path: &ast::PathExpr, 133 path: &ast::PathExpr,
109 module: &Module, 134 module: &Module,
110 enum_module_def: &ModuleDef, 135 enum_module_def: &ModuleDef,
@@ -116,69 +141,68 @@ fn insert_import(
116 mod_path.segments.pop(); 141 mod_path.segments.pop();
117 mod_path.segments.push(variant_hir_name.clone()); 142 mod_path.segments.push(variant_hir_name.clone());
118 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 143 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
119 let syntax = scope.as_syntax_node();
120 144
121 let new_syntax = 145 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
122 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
123 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
124 builder.replace(syntax.text_range(), new_syntax.to_string())
125 } 146 }
126 Some(()) 147 Some(())
127} 148}
128 149
129// FIXME: this should use strongly-typed `make`, rather than string manipulation.
130fn extract_struct_def( 150fn extract_struct_def(
131 builder: &mut AssistBuilder, 151 rewriter: &mut SyntaxRewriter,
132 enum_: &ast::Enum, 152 enum_: &ast::Enum,
133 variant_name: &str, 153 variant_name: ast::Name,
134 variant_list: &str, 154 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
135 start_offset: TextSize, 155 start_offset: &SyntaxElement,
136 file_id: FileId, 156 visibility: Option<ast::Visibility>,
137 visibility: &Option<ast::Visibility>,
138) -> Option<()> { 157) -> Option<()> {
139 let visibility_string = if let Some(visibility) = visibility { 158 let pub_vis = Some(make::visibility_pub());
140 format!("{} ", visibility.to_string()) 159 let field_list = match field_list {
141 } else { 160 Either::Left(field_list) => {
142 "".to_string() 161 make::record_field_list(field_list.fields().flat_map(|field| {
162 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
163 }))
164 .into()
165 }
166 Either::Right(field_list) => make::tuple_field_list(
167 field_list
168 .fields()
169 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
170 )
171 .into(),
143 }; 172 };
144 let indent = IndentLevel::from_node(enum_.syntax()); 173
145 let struct_def = format!( 174 rewriter.insert_before(
146 r#"{}struct {}{}; 175 start_offset,
147 176 make::struct_(visibility, variant_name, None, field_list).syntax(),
148{}"#,
149 visibility_string,
150 variant_name,
151 list_with_visibility(variant_list),
152 indent
153 ); 177 );
154 builder.edit_file(file_id); 178 rewriter.insert_before(start_offset, &make::tokens::blank_line());
155 builder.insert(start_offset, struct_def); 179
180 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
181 rewriter
182 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
183 }
156 Some(()) 184 Some(())
157} 185}
158 186
159fn update_variant( 187fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
160 builder: &mut AssistBuilder, 188 let name = variant.name()?;
161 variant_name: &str, 189 let tuple_field = make::tuple_field(None, make::ty(name.text()));
162 file_id: FileId, 190 let replacement = make::variant(
163 list_range: TextRange, 191 name,
164) -> Option<()> { 192 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
165 let inside_variant_range = TextRange::new(
166 list_range.start().checked_add(TextSize::from(1))?,
167 list_range.end().checked_sub(TextSize::from(1))?,
168 ); 193 );
169 builder.edit_file(file_id); 194 rewriter.replace(variant.syntax(), replacement.syntax());
170 builder.replace(inside_variant_range, variant_name);
171 Some(()) 195 Some(())
172} 196}
173 197
174fn update_reference( 198fn update_reference(
175 ctx: &AssistContext, 199 ctx: &AssistContext,
176 builder: &mut AssistBuilder, 200 rewriter: &mut SyntaxRewriter,
177 reference: Reference, 201 reference: Reference,
178 source_file: &SourceFile, 202 source_file: &SourceFile,
179 _enum_module_def: &ModuleDef, 203 enum_module_def: &ModuleDef,
180 _variant_hir_name: &Name, 204 variant_hir_name: &Name,
181 _visited_modules_set: &mut FxHashSet<Module>, 205 visited_modules_set: &mut FxHashSet<Module>,
182) -> Option<()> { 206) -> Option<()> {
183 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 207 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
184 source_file.syntax(), 208 source_file.syntax(),
@@ -187,35 +211,21 @@ fn update_reference(
187 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 211 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
188 let list = call.arg_list()?; 212 let list = call.arg_list()?;
189 let segment = path_expr.path()?.segment()?; 213 let segment = path_expr.path()?.segment()?;
190 let _module = ctx.sema.scope(&path_expr.syntax()).module()?; 214 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
191 let list_range = list.syntax().text_range();
192 let inside_list_range = TextRange::new(
193 list_range.start().checked_add(TextSize::from(1))?,
194 list_range.end().checked_sub(TextSize::from(1))?,
195 );
196 builder.edit_file(reference.file_range.file_id);
197 /* FIXME: this most likely requires AST-based editing, see `insert_import`
198 if !visited_modules_set.contains(&module) { 215 if !visited_modules_set.contains(&module) {
199 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) 216 if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
200 .is_some() 217 .is_some()
201 { 218 {
202 visited_modules_set.insert(module); 219 visited_modules_set.insert(module);
203 } 220 }
204 } 221 }
205 */
206 builder.replace(inside_list_range, format!("{}{}", segment, list));
207 Some(())
208}
209 222
210fn list_with_visibility(list: &str) -> String { 223 let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
211 list.split(',') 224 let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
212 .map(|part| { 225 rewriter.insert_after(&lparen, segment.syntax());
213 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 }; 226 rewriter.insert_after(&lparen, &lparen);
214 let mut mod_part = part.trim().to_string(); 227 rewriter.insert_before(&rparen, &rparen);
215 mod_part.insert_str(index, "pub "); 228 Some(())
216 mod_part
217 })
218 .join(", ")
219} 229}
220 230
221#[cfg(test)] 231#[cfg(test)]
@@ -228,7 +238,7 @@ mod tests {
228 use super::*; 238 use super::*;
229 239
230 #[test] 240 #[test]
231 fn test_extract_struct_several_fields() { 241 fn test_extract_struct_several_fields_tuple() {
232 check_assist( 242 check_assist(
233 extract_struct_from_enum_variant, 243 extract_struct_from_enum_variant,
234 "enum A { <|>One(u32, u32) }", 244 "enum A { <|>One(u32, u32) }",
@@ -239,6 +249,41 @@ enum A { One(One) }"#,
239 } 249 }
240 250
241 #[test] 251 #[test]
252 fn test_extract_struct_several_fields_named() {
253 check_assist(
254 extract_struct_from_enum_variant,
255 "enum A { <|>One { foo: u32, bar: u32 } }",
256 r#"struct One{ pub foo: u32, pub bar: u32 }
257
258enum A { One(One) }"#,
259 );
260 }
261
262 #[test]
263 fn test_extract_struct_one_field_named() {
264 check_assist(
265 extract_struct_from_enum_variant,
266 "enum A { <|>One { foo: u32 } }",
267 r#"struct One{ pub foo: u32 }
268
269enum A { One(One) }"#,
270 );
271 }
272
273 #[test]
274 fn test_extract_enum_variant_name_value_namespace() {
275 check_assist(
276 extract_struct_from_enum_variant,
277 r#"const One: () = ();
278enum A { <|>One(u32, u32) }"#,
279 r#"const One: () = ();
280struct One(pub u32, pub u32);
281
282enum A { One(One) }"#,
283 );
284 }
285
286 #[test]
242 fn test_extract_struct_pub_visibility() { 287 fn test_extract_struct_pub_visibility() {
243 check_assist( 288 check_assist(
244 extract_struct_from_enum_variant, 289 extract_struct_from_enum_variant,
@@ -250,7 +295,6 @@ pub enum A { One(One) }"#,
250 } 295 }
251 296
252 #[test] 297 #[test]
253 #[ignore] // FIXME: this currently panics if `insert_import` is used
254 fn test_extract_struct_with_complex_imports() { 298 fn test_extract_struct_with_complex_imports() {
255 check_assist( 299 check_assist(
256 extract_struct_from_enum_variant, 300 extract_struct_from_enum_variant,
@@ -316,7 +360,7 @@ fn another_fn() {
316 fn test_extract_enum_not_applicable_if_struct_exists() { 360 fn test_extract_enum_not_applicable_if_struct_exists() {
317 check_not_applicable( 361 check_not_applicable(
318 r#"struct One; 362 r#"struct One;
319 enum A { <|>One(u8) }"#, 363 enum A { <|>One(u8, u32) }"#,
320 ); 364 );
321 } 365 }
322 366
@@ -324,4 +368,14 @@ fn another_fn() {
324 fn test_extract_not_applicable_one_field() { 368 fn test_extract_not_applicable_one_field() {
325 check_not_applicable(r"enum A { <|>One(u32) }"); 369 check_not_applicable(r"enum A { <|>One(u32) }");
326 } 370 }
371
372 #[test]
373 fn test_extract_not_applicable_no_field_tuple() {
374 check_not_applicable(r"enum A { <|>None() }");
375 }
376
377 #[test]
378 fn test_extract_not_applicable_no_field_named() {
379 check_not_applicable(r"enum A { <|>None {} }");
380 }
327} 381}
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
index 5c69db53e..64b4b1a76 100644
--- a/crates/assists/src/handlers/flip_comma.rs
+++ b/crates/assists/src/handlers/flip_comma.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_syntax_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24 24
diff --git a/crates/assists/src/handlers/flip_trait_bound.rs b/crates/assists/src/handlers/flip_trait_bound.rs
index 347e79b1d..92ee42181 100644
--- a/crates/assists/src/handlers/flip_trait_bound.rs
+++ b/crates/assists/src/handlers/flip_trait_bound.rs
@@ -20,7 +20,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_syntax_at_offset(T![+])?;
24 24
25 // Make sure we're in a `TypeBoundList` 25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() { 26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
diff --git a/crates/assists/src/handlers/infer_function_return_type.rs b/crates/assists/src/handlers/infer_function_return_type.rs
new file mode 100644
index 000000000..520d07ae0
--- /dev/null
+++ b/crates/assists/src/handlers/infer_function_return_type.rs
@@ -0,0 +1,337 @@
1use hir::HirDisplay;
2use syntax::{ast, AstNode, TextRange, TextSize};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: infer_function_return_type
8//
9// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
10// type specified. This assists is useable in a functions or closures tail expression or return type position.
11//
12// ```
13// fn foo() { 4<|>2i32 }
14// ```
15// ->
16// ```
17// fn foo() -> i32 { 42i32 }
18// ```
19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let (tail_expr, builder_edit_pos, wrap_expr) = extract_tail(ctx)?;
21 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
22 let ty = ctx.sema.type_of_expr(&tail_expr)?;
23 if ty.is_unit() {
24 return None;
25 }
26 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
27
28 acc.add(
29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
30 "Add this function's return type",
31 tail_expr.syntax().text_range(),
32 |builder| {
33 match builder_edit_pos {
34 InsertOrReplace::Insert(insert_pos) => {
35 builder.insert(insert_pos, &format!("-> {} ", ty))
36 }
37 InsertOrReplace::Replace(text_range) => {
38 builder.replace(text_range, &format!("-> {}", ty))
39 }
40 }
41 if wrap_expr {
42 mark::hit!(wrap_closure_non_block_expr);
43 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
44 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
45 }
46 },
47 )
48}
49
50enum InsertOrReplace {
51 Insert(TextSize),
52 Replace(TextRange),
53}
54
55/// Check the potentially already specified return type and reject it or turn it into a builder command
56/// if allowed.
57fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
58 match ret_ty {
59 Some(ret_ty) => match ret_ty.ty() {
60 Some(ast::Type::InferType(_)) | None => {
61 mark::hit!(existing_infer_ret_type);
62 mark::hit!(existing_infer_ret_type_closure);
63 Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
64 }
65 _ => {
66 mark::hit!(existing_ret_type);
67 mark::hit!(existing_ret_type_closure);
68 None
69 }
70 },
71 None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
72 }
73}
74
75fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool)> {
76 let (tail_expr, return_type_range, action, wrap_expr) =
77 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
78 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
79 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
80
81 let body = closure.body()?;
82 let body_start = body.syntax().first_token()?.text_range().start();
83 let (tail_expr, wrap_expr) = match body {
84 ast::Expr::BlockExpr(block) => (block.expr()?, false),
85 body => (body, true),
86 };
87
88 let ret_range = TextRange::new(rpipe_pos, body_start);
89 (tail_expr, ret_range, action, wrap_expr)
90 } else {
91 let func = ctx.find_node_at_offset::<ast::Fn>()?;
92 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
93 let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
94
95 let body = func.body()?;
96 let tail_expr = body.expr()?;
97
98 let ret_range_end = body.l_curly_token()?.text_range().start();
99 let ret_range = TextRange::new(rparen_pos, ret_range_end);
100 (tail_expr, ret_range, action, false)
101 };
102 let frange = ctx.frange.range;
103 if return_type_range.contains_range(frange) {
104 mark::hit!(cursor_in_ret_position);
105 mark::hit!(cursor_in_ret_position_closure);
106 } else if tail_expr.syntax().text_range().contains_range(frange) {
107 mark::hit!(cursor_on_tail);
108 mark::hit!(cursor_on_tail_closure);
109 } else {
110 return None;
111 }
112 Some((tail_expr, action, wrap_expr))
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::tests::{check_assist, check_assist_not_applicable};
118
119 use super::*;
120
121 #[test]
122 fn infer_return_type_specified_inferred() {
123 mark::check!(existing_infer_ret_type);
124 check_assist(
125 infer_function_return_type,
126 r#"fn foo() -> <|>_ {
127 45
128}"#,
129 r#"fn foo() -> i32 {
130 45
131}"#,
132 );
133 }
134
135 #[test]
136 fn infer_return_type_specified_inferred_closure() {
137 mark::check!(existing_infer_ret_type_closure);
138 check_assist(
139 infer_function_return_type,
140 r#"fn foo() {
141 || -> _ {<|>45};
142}"#,
143 r#"fn foo() {
144 || -> i32 {45};
145}"#,
146 );
147 }
148
149 #[test]
150 fn infer_return_type_cursor_at_return_type_pos() {
151 mark::check!(cursor_in_ret_position);
152 check_assist(
153 infer_function_return_type,
154 r#"fn foo() <|>{
155 45
156}"#,
157 r#"fn foo() -> i32 {
158 45
159}"#,
160 );
161 }
162
163 #[test]
164 fn infer_return_type_cursor_at_return_type_pos_closure() {
165 mark::check!(cursor_in_ret_position_closure);
166 check_assist(
167 infer_function_return_type,
168 r#"fn foo() {
169 || <|>45
170}"#,
171 r#"fn foo() {
172 || -> i32 {45}
173}"#,
174 );
175 }
176
177 #[test]
178 fn infer_return_type() {
179 mark::check!(cursor_on_tail);
180 check_assist(
181 infer_function_return_type,
182 r#"fn foo() {
183 45<|>
184}"#,
185 r#"fn foo() -> i32 {
186 45
187}"#,
188 );
189 }
190
191 #[test]
192 fn infer_return_type_nested() {
193 check_assist(
194 infer_function_return_type,
195 r#"fn foo() {
196 if true {
197 3<|>
198 } else {
199 5
200 }
201}"#,
202 r#"fn foo() -> i32 {
203 if true {
204 3
205 } else {
206 5
207 }
208}"#,
209 );
210 }
211
212 #[test]
213 fn not_applicable_ret_type_specified() {
214 mark::check!(existing_ret_type);
215 check_assist_not_applicable(
216 infer_function_return_type,
217 r#"fn foo() -> i32 {
218 ( 45<|> + 32 ) * 123
219}"#,
220 );
221 }
222
223 #[test]
224 fn not_applicable_non_tail_expr() {
225 check_assist_not_applicable(
226 infer_function_return_type,
227 r#"fn foo() {
228 let x = <|>3;
229 ( 45 + 32 ) * 123
230}"#,
231 );
232 }
233
234 #[test]
235 fn not_applicable_unit_return_type() {
236 check_assist_not_applicable(
237 infer_function_return_type,
238 r#"fn foo() {
239 (<|>)
240}"#,
241 );
242 }
243
244 #[test]
245 fn infer_return_type_closure_block() {
246 mark::check!(cursor_on_tail_closure);
247 check_assist(
248 infer_function_return_type,
249 r#"fn foo() {
250 |x: i32| {
251 x<|>
252 };
253}"#,
254 r#"fn foo() {
255 |x: i32| -> i32 {
256 x
257 };
258}"#,
259 );
260 }
261
262 #[test]
263 fn infer_return_type_closure() {
264 check_assist(
265 infer_function_return_type,
266 r#"fn foo() {
267 |x: i32| { x<|> };
268}"#,
269 r#"fn foo() {
270 |x: i32| -> i32 { x };
271}"#,
272 );
273 }
274
275 #[test]
276 fn infer_return_type_closure_wrap() {
277 mark::check!(wrap_closure_non_block_expr);
278 check_assist(
279 infer_function_return_type,
280 r#"fn foo() {
281 |x: i32| x<|>;
282}"#,
283 r#"fn foo() {
284 |x: i32| -> i32 {x};
285}"#,
286 );
287 }
288
289 #[test]
290 fn infer_return_type_nested_closure() {
291 check_assist(
292 infer_function_return_type,
293 r#"fn foo() {
294 || {
295 if true {
296 3<|>
297 } else {
298 5
299 }
300 }
301}"#,
302 r#"fn foo() {
303 || -> i32 {
304 if true {
305 3
306 } else {
307 5
308 }
309 }
310}"#,
311 );
312 }
313
314 #[test]
315 fn not_applicable_ret_type_specified_closure() {
316 mark::check!(existing_ret_type_closure);
317 check_assist_not_applicable(
318 infer_function_return_type,
319 r#"fn foo() {
320 || -> i32 { 3<|> }
321}"#,
322 );
323 }
324
325 #[test]
326 fn not_applicable_non_tail_expr_closure() {
327 check_assist_not_applicable(
328 infer_function_return_type,
329 r#"fn foo() {
330 || -> i32 {
331 let x = 3<|>;
332 6
333 }
334}"#,
335 );
336 }
337}
diff --git a/crates/assists/src/handlers/introduce_named_lifetime.rs b/crates/assists/src/handlers/introduce_named_lifetime.rs
index 5f623e5f7..4cc8dae65 100644
--- a/crates/assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/assists/src/handlers/introduce_named_lifetime.rs
@@ -36,7 +36,7 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo 36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime_token = ctx 38 let lifetime_token = ctx
39 .find_token_at_offset(SyntaxKind::LIFETIME) 39 .find_token_syntax_at_offset(SyntaxKind::LIFETIME)
40 .filter(|lifetime| lifetime.text() == "'_")?; 40 .filter(|lifetime| lifetime.text() == "'_")?;
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) { 41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs
index 461fcf862..ea722b91b 100644
--- a/crates/assists/src/handlers/invert_if.rs
+++ b/crates/assists/src/handlers/invert_if.rs
@@ -29,7 +29,7 @@ use crate::{
29// ``` 29// ```
30 30
31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let if_keyword = ctx.find_token_at_offset(T![if])?; 32 let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
33 let expr = ast::IfExpr::cast(if_keyword.parent())?; 33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
34 let if_range = if_keyword.text_range(); 34 let if_range = if_keyword.text_range();
35 let cursor_in_range = if_range.contains_range(ctx.frange.range); 35 let cursor_in_range = if_range.contains_range(ctx.frange.range);
diff --git a/crates/assists/src/handlers/raw_string.rs b/crates/assists/src/handlers/raw_string.rs
index 9ddd116e0..4c759cc25 100644
--- a/crates/assists/src/handlers/raw_string.rs
+++ b/crates/assists/src/handlers/raw_string.rs
@@ -1,11 +1,6 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use syntax::{ 3use syntax::{ast, AstToken, TextRange, TextSize};
4 ast::{self, HasQuotes, HasStringValue},
5 AstToken,
6 SyntaxKind::{RAW_STRING, STRING},
7 TextRange, TextSize,
8};
9use test_utils::mark; 4use test_utils::mark;
10 5
11use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -26,7 +21,10 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
26// } 21// }
27// ``` 22// ```
28pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 24 let token = ctx.find_token_at_offset::<ast::String>()?;
25 if token.is_raw() {
26 return None;
27 }
30 let value = token.value()?; 28 let value = token.value()?;
31 let target = token.syntax().text_range(); 29 let target = token.syntax().text_range();
32 acc.add( 30 acc.add(
@@ -65,7 +63,10 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
65// } 63// }
66// ``` 64// ```
67pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 65pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
68 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 66 let token = ctx.find_token_at_offset::<ast::String>()?;
67 if !token.is_raw() {
68 return None;
69 }
69 let value = token.value()?; 70 let value = token.value()?;
70 let target = token.syntax().text_range(); 71 let target = token.syntax().text_range();
71 acc.add( 72 acc.add(
@@ -104,11 +105,15 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
104// } 105// }
105// ``` 106// ```
106pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 107pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
107 let token = ctx.find_token_at_offset(RAW_STRING)?; 108 let token = ctx.find_token_at_offset::<ast::String>()?;
108 let target = token.text_range(); 109 if !token.is_raw() {
110 return None;
111 }
112 let text_range = token.syntax().text_range();
113 let target = text_range;
109 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| { 114 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
110 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 115 edit.insert(text_range.start() + TextSize::of('r'), "#");
111 edit.insert(token.text_range().end(), "#"); 116 edit.insert(text_range.end(), "#");
112 }) 117 })
113} 118}
114 119
@@ -128,7 +133,10 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
128// } 133// }
129// ``` 134// ```
130pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 135pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
131 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 136 let token = ctx.find_token_at_offset::<ast::String>()?;
137 if !token.is_raw() {
138 return None;
139 }
132 140
133 let text = token.text().as_str(); 141 let text = token.text().as_str();
134 if !text.starts_with("r#") && text.ends_with('#') { 142 if !text.starts_with("r#") && text.ends_with('#') {
diff --git a/crates/assists/src/handlers/remove_mut.rs b/crates/assists/src/handlers/remove_mut.rs
index 44f41daa9..575b271f7 100644
--- a/crates/assists/src/handlers/remove_mut.rs
+++ b/crates/assists/src/handlers/remove_mut.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
18// } 18// }
19// ``` 19// ```
20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?; 21 let mut_token = ctx.find_token_syntax_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start(); 22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() { 23 let delete_to = match mut_token.next_token() {
24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(), 24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index a5bcbda24..69d3b08d3 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -37,7 +37,7 @@ use ide_db::ty_filter::TryEnum;
37// fn compute() -> Option<i32> { None } 37// fn compute() -> Option<i32> { None }
38// ``` 38// ```
39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let let_kw = ctx.find_token_at_offset(T![let])?; 40 let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; 41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
42 let init = let_stmt.initializer()?; 42 let init = let_stmt.initializer()?;
43 let original_pat = let_stmt.pat()?; 43 let original_pat = let_stmt.pat()?;
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index c50bc7604..d7e1d9580 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -45,10 +45,9 @@ pub(crate) fn replace_qualified_name_with_use(
45 // affected (that is, all paths inside the node we added the `use` to). 45 // affected (that is, all paths inside the node we added the `use` to).
46 let mut rewriter = SyntaxRewriter::default(); 46 let mut rewriter = SyntaxRewriter::default();
47 shorten_paths(&mut rewriter, syntax.clone(), &path); 47 shorten_paths(&mut rewriter, syntax.clone(), &path);
48 let rewritten_syntax = rewriter.rewrite(&syntax); 48 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
49 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) { 49 rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
50 let new_syntax = insert_use(import_scope, path, ctx.config.insert_use.merge); 50 builder.rewrite(rewriter);
51 builder.replace(syntax.text_range(), new_syntax.to_string())
52 } 51 }
53 }, 52 },
54 ) 53 )
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs
index 4ca87a8ec..b4b898846 100644
--- a/crates/assists/src/handlers/replace_string_with_char.rs
+++ b/crates/assists/src/handlers/replace_string_with_char.rs
@@ -1,8 +1,4 @@
1use syntax::{ 1use syntax::{ast, AstToken, SyntaxKind::STRING};
2 ast::{self, HasStringValue},
3 AstToken,
4 SyntaxKind::STRING,
5};
6 2
7use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
8 4
@@ -22,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// } 18// }
23// ``` 19// ```
24pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 21 let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
26 let value = token.value()?; 22 let value = token.value()?;
27 let target = token.syntax().text_range(); 23 let target = token.syntax().text_range();
28 24
diff --git a/crates/assists/src/handlers/split_import.rs b/crates/assists/src/handlers/split_import.rs
index 15e67eaa1..ef1f6b8a1 100644
--- a/crates/assists/src/handlers/split_import.rs
+++ b/crates/assists/src/handlers/split_import.rs
@@ -16,7 +16,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
16// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
17// ``` 17// ```
18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; 20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
22 22
diff --git a/crates/assists/src/handlers/unwrap_block.rs b/crates/assists/src/handlers/unwrap_block.rs
index 3851aeb3e..36ef871b9 100644
--- a/crates/assists/src/handlers/unwrap_block.rs
+++ b/crates/assists/src/handlers/unwrap_block.rs
@@ -29,7 +29,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite); 29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30 let assist_label = "Unwrap block"; 30 let assist_label = "Unwrap block";
31 31
32 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; 33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
34 let mut parent = block.syntax().parent()?; 34 let mut parent = block.syntax().parent()?;
35 if ast::MatchArm::can_cast(parent.kind()) { 35 if ast::MatchArm::can_cast(parent.kind()) {
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index b804e495d..af88b3437 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -143,6 +143,7 @@ mod handlers {
143 mod generate_function; 143 mod generate_function;
144 mod generate_impl; 144 mod generate_impl;
145 mod generate_new; 145 mod generate_new;
146 mod infer_function_return_type;
146 mod inline_local_variable; 147 mod inline_local_variable;
147 mod introduce_named_lifetime; 148 mod introduce_named_lifetime;
148 mod invert_if; 149 mod invert_if;
@@ -190,6 +191,7 @@ mod handlers {
190 generate_function::generate_function, 191 generate_function::generate_function,
191 generate_impl::generate_impl, 192 generate_impl::generate_impl,
192 generate_new::generate_new, 193 generate_new::generate_new,
194 infer_function_return_type::infer_function_return_type,
193 inline_local_variable::inline_local_variable, 195 inline_local_variable::inline_local_variable,
194 introduce_named_lifetime::introduce_named_lifetime, 196 introduce_named_lifetime::introduce_named_lifetime,
195 invert_if::invert_if, 197 invert_if::invert_if,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index acbf5b652..168e1626a 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -506,6 +506,19 @@ impl<T: Clone> Ctx<T> {
506} 506}
507 507
508#[test] 508#[test]
509fn doctest_infer_function_return_type() {
510 check_doc_test(
511 "infer_function_return_type",
512 r#####"
513fn foo() { 4<|>2i32 }
514"#####,
515 r#####"
516fn foo() -> i32 { 42i32 }
517"#####,
518 )
519}
520
521#[test]
509fn doctest_inline_local_variable() { 522fn doctest_inline_local_variable() {
510 check_doc_test( 523 check_doc_test(
511 "inline_local_variable", 524 "inline_local_variable",
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index a76bd5ebf..84a0dffdd 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,12 +1,9 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2use std::{ 2use std::{cmp::Ordering, iter::successors};
3 cmp::Ordering,
4 iter::{self, successors},
5};
6 3
7use itertools::{EitherOrBoth, Itertools}; 4use itertools::{EitherOrBoth, Itertools};
8use syntax::{ 5use syntax::{
9 algo, 6 algo::SyntaxRewriter,
10 ast::{ 7 ast::{
11 self, 8 self,
12 edit::{AstNodeEdit, IndentLevel}, 9 edit::{AstNodeEdit, IndentLevel},
@@ -88,20 +85,19 @@ impl ImportScope {
88} 85}
89 86
90/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 87/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
91pub(crate) fn insert_use( 88pub(crate) fn insert_use<'a>(
92 scope: &ImportScope, 89 scope: &ImportScope,
93 path: ast::Path, 90 path: ast::Path,
94 merge: Option<MergeBehaviour>, 91 merge: Option<MergeBehaviour>,
95) -> SyntaxNode { 92) -> SyntaxRewriter<'a> {
93 let mut rewriter = SyntaxRewriter::default();
96 let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); 94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
97 // merge into existing imports if possible 95 // merge into existing imports if possible
98 if let Some(mb) = merge { 96 if let Some(mb) = merge {
99 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
100 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
101 let to_delete: SyntaxElement = existing_use.syntax().clone().into(); 99 rewriter.replace(existing_use.syntax(), merged.syntax());
102 let to_delete = to_delete.clone()..=to_delete; 100 return rewriter;
103 let to_insert = iter::once(merged.syntax().clone().into());
104 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
105 } 101 }
106 } 102 }
107 } 103 }
@@ -157,7 +153,15 @@ pub(crate) fn insert_use(
157 buf 153 buf
158 }; 154 };
159 155
160 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert) 156 match insert_position {
157 InsertPosition::First => {
158 rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
159 }
160 InsertPosition::Last => return rewriter, // actually unreachable
161 InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
162 InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
163 }
164 rewriter
161} 165}
162 166
163fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 167fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -1101,7 +1105,8 @@ use foo::bar::baz::Qux;",
1101 .find_map(ast::Path::cast) 1105 .find_map(ast::Path::cast)
1102 .unwrap(); 1106 .unwrap();
1103 1107
1104 let result = insert_use(&file, path, mb).to_string(); 1108 let rewriter = insert_use(&file, path, mb);
1109 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
1105 assert_eq_text!(&result, ra_fixture_after); 1110 assert_eq_text!(&result, ra_fixture_after);
1106 } 1111 }
1107 1112
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 1deaa90f2..cd7958746 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -953,18 +953,19 @@ impl From<ast::BinOp> for BinaryOp {
953impl From<ast::LiteralKind> for Literal { 953impl From<ast::LiteralKind> for Literal {
954 fn from(ast_lit_kind: ast::LiteralKind) -> Self { 954 fn from(ast_lit_kind: ast::LiteralKind) -> Self {
955 match ast_lit_kind { 955 match ast_lit_kind {
956 LiteralKind::IntNumber { suffix } => { 956 LiteralKind::IntNumber(lit) => {
957 let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it)); 957 if let Some(float_suffix) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
958 958 return Literal::Float(Default::default(), Some(float_suffix));
959 Literal::Int(Default::default(), known_name) 959 }
960 let ty = lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it));
961 Literal::Int(Default::default(), ty)
960 } 962 }
961 LiteralKind::FloatNumber { suffix } => { 963 LiteralKind::FloatNumber(lit) => {
962 let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it)); 964 let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it));
963 965 Literal::Float(Default::default(), ty)
964 Literal::Float(Default::default(), known_name)
965 } 966 }
966 LiteralKind::ByteString => Literal::ByteString(Default::default()), 967 LiteralKind::ByteString(_) => Literal::ByteString(Default::default()),
967 LiteralKind::String => Literal::String(Default::default()), 968 LiteralKind::String(_) => Literal::String(Default::default()),
968 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)), 969 LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
969 LiteralKind::Bool(val) => Literal::Bool(val), 970 LiteralKind::Bool(val) => Literal::Bool(val),
970 LiteralKind::Char => Literal::Char(Default::default()), 971 LiteralKind::Char => Literal::Char(Default::default()),
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 59b6644c3..386287518 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1116,17 +1116,20 @@ impl ModCollector<'_, '_> {
1116 &self.item_tree[module.visibility], 1116 &self.item_tree[module.visibility],
1117 ); 1117 );
1118 1118
1119 ModCollector { 1119 if let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr)
1120 def_collector: &mut *self.def_collector, 1120 {
1121 macro_depth: self.macro_depth, 1121 ModCollector {
1122 module_id, 1122 def_collector: &mut *self.def_collector,
1123 file_id: self.file_id, 1123 macro_depth: self.macro_depth,
1124 item_tree: self.item_tree, 1124 module_id,
1125 mod_dir: self.mod_dir.descend_into_definition(&module.name, path_attr), 1125 file_id: self.file_id,
1126 } 1126 item_tree: self.item_tree,
1127 .collect(&*items); 1127 mod_dir,
1128 if is_macro_use { 1128 }
1129 self.import_all_legacy_macros(module_id); 1129 .collect(&*items);
1130 if is_macro_use {
1131 self.import_all_legacy_macros(module_id);
1132 }
1130 } 1133 }
1131 } 1134 }
1132 // out of line module, resolve, parse and recurse 1135 // out of line module, resolve, parse and recurse
diff --git a/crates/hir_def/src/nameres/mod_resolution.rs b/crates/hir_def/src/nameres/mod_resolution.rs
index e8389b484..c0c789cae 100644
--- a/crates/hir_def/src/nameres/mod_resolution.rs
+++ b/crates/hir_def/src/nameres/mod_resolution.rs
@@ -2,9 +2,12 @@
2use base_db::FileId; 2use base_db::FileId;
3use hir_expand::name::Name; 3use hir_expand::name::Name;
4use syntax::SmolStr; 4use syntax::SmolStr;
5use test_utils::mark;
5 6
6use crate::{db::DefDatabase, HirFileId}; 7use crate::{db::DefDatabase, HirFileId};
7 8
9const MOD_DEPTH_LIMIT: u32 = 32;
10
8#[derive(Clone, Debug)] 11#[derive(Clone, Debug)]
9pub(super) struct ModDir { 12pub(super) struct ModDir {
10 /// `` for `mod.rs`, `lib.rs` 13 /// `` for `mod.rs`, `lib.rs`
@@ -14,18 +17,28 @@ pub(super) struct ModDir {
14 dir_path: DirPath, 17 dir_path: DirPath,
15 /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` 18 /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
16 root_non_dir_owner: bool, 19 root_non_dir_owner: bool,
20 depth: u32,
17} 21}
18 22
19impl ModDir { 23impl ModDir {
20 pub(super) fn root() -> ModDir { 24 pub(super) fn root() -> ModDir {
21 ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false } 25 ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false, depth: 0 }
26 }
27 fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> {
28 let depth = self.depth + 1;
29 if depth > MOD_DEPTH_LIMIT {
30 log::error!("MOD_DEPTH_LIMIT exceeded");
31 mark::hit!(circular_mods);
32 return None;
33 }
34 Some(ModDir { dir_path, root_non_dir_owner, depth })
22 } 35 }
23 36
24 pub(super) fn descend_into_definition( 37 pub(super) fn descend_into_definition(
25 &self, 38 &self,
26 name: &Name, 39 name: &Name,
27 attr_path: Option<&SmolStr>, 40 attr_path: Option<&SmolStr>,
28 ) -> ModDir { 41 ) -> Option<ModDir> {
29 let path = match attr_path.map(|it| it.as_str()) { 42 let path = match attr_path.map(|it| it.as_str()) {
30 None => { 43 None => {
31 let mut path = self.dir_path.clone(); 44 let mut path = self.dir_path.clone();
@@ -40,7 +53,7 @@ impl ModDir {
40 DirPath::new(path) 53 DirPath::new(path)
41 } 54 }
42 }; 55 };
43 ModDir { dir_path: path, root_non_dir_owner: false } 56 self.child(path, false)
44 } 57 }
45 58
46 pub(super) fn resolve_declaration( 59 pub(super) fn resolve_declaration(
@@ -72,7 +85,9 @@ impl ModDir {
72 } else { 85 } else {
73 (DirPath::new(format!("{}/", name)), true) 86 (DirPath::new(format!("{}/", name)), true)
74 }; 87 };
75 return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner })); 88 if let Some(mod_dir) = self.child(dir_path, root_non_dir_owner) {
89 return Ok((file_id, is_mod_rs, mod_dir));
90 }
76 } 91 }
77 } 92 }
78 Err(candidate_files.remove(0)) 93 Err(candidate_files.remove(0))
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index 9c19bf572..a4d1fb8f3 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -20,9 +20,8 @@ fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> {
20} 20}
21 21
22fn check(ra_fixture: &str, expect: Expect) { 22fn check(ra_fixture: &str, expect: Expect) {
23 let db = TestDB::with_files(ra_fixture); 23 let def_map = compute_crate_def_map(ra_fixture);
24 let krate = db.crate_graph().iter().next().unwrap(); 24 let actual = def_map.dump();
25 let actual = db.crate_def_map(krate).dump();
26 expect.assert_eq(&actual); 25 expect.assert_eq(&actual);
27} 26}
28 27
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs
index f93337a6e..ba295fd9e 100644
--- a/crates/hir_def/src/nameres/tests/mod_resolution.rs
+++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs
@@ -323,13 +323,26 @@ pub struct Baz;
323fn module_resolution_relative_path_outside_root() { 323fn module_resolution_relative_path_outside_root() {
324 check( 324 check(
325 r#" 325 r#"
326//- /main.rs 326//- /a/b/c/d/e/main.rs crate:main
327#[path="../../../../../outside.rs"] 327#[path="../../../../../outside.rs"]
328mod foo; 328mod foo;
329
330//- /outside.rs
331mod bar;
332
333//- /bar.rs
334pub struct Baz;
329"#, 335"#,
330 expect![[r#" 336 expect![[r#"
331 crate 337 crate
332 "#]], 338 foo: t
339
340 crate::foo
341 bar: t
342
343 crate::foo::bar
344 Baz: t v
345"#]],
333 ); 346 );
334} 347}
335 348
@@ -758,3 +771,30 @@ struct X;
758 "#]], 771 "#]],
759 ); 772 );
760} 773}
774
775#[test]
776fn circular_mods() {
777 mark::check!(circular_mods);
778 compute_crate_def_map(
779 r#"
780//- /lib.rs
781mod foo;
782//- /foo.rs
783#[path = "./foo.rs"]
784mod foo;
785"#,
786 );
787
788 compute_crate_def_map(
789 r#"
790//- /lib.rs
791mod foo;
792//- /foo.rs
793#[path = "./bar.rs"]
794mod bar;
795//- /bar.rs
796#[path = "./foo.rs"]
797mod foo;
798"#,
799 );
800}
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index 86918b626..aebbfc4df 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -8,7 +8,7 @@ use base_db::FileId;
8use either::Either; 8use either::Either;
9use mbe::parse_to_token_tree; 9use mbe::parse_to_token_tree;
10use parser::FragmentKind; 10use parser::FragmentKind;
11use syntax::ast::{self, AstToken, HasStringValue}; 11use syntax::ast::{self, AstToken};
12 12
13macro_rules! register_builtin { 13macro_rules! register_builtin {
14 ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { 14 ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 02e17ba43..d275dd75b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -157,7 +157,8 @@ fn missing_record_expr_field_fix(
157 return None; 157 return None;
158 } 158 }
159 let new_field = make::record_field( 159 let new_field = make::record_field(
160 record_expr_field.field_name()?, 160 None,
161 make::name(record_expr_field.field_name()?.text()),
161 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), 162 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
162 ); 163 );
163 164
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 3ee0af8ad..0971f7701 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -35,7 +35,7 @@ fn try_extend_selection(
35) -> Option<TextRange> { 35) -> Option<TextRange> {
36 let range = frange.range; 36 let range = frange.range;
37 37
38 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; 38 let string_kinds = [COMMENT, STRING, BYTE_STRING];
39 let list_kinds = [ 39 let list_kinds = [
40 RECORD_PAT_FIELD_LIST, 40 RECORD_PAT_FIELD_LIST,
41 MATCH_ARM_LIST, 41 MATCH_ARM_LIST,
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index ac704ae21..6cfb22e13 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -162,7 +162,7 @@ fn get_param_name_hints(
162 .zip(args) 162 .zip(args)
163 .filter_map(|((param, _ty), arg)| { 163 .filter_map(|((param, _ty), arg)| {
164 let param_name = match param? { 164 let param_name = match param? {
165 Either::Left(self_param) => self_param.to_string(), 165 Either::Left(_) => "self".to_string(),
166 Either::Right(pat) => match pat { 166 Either::Right(pat) => match pat {
167 ast::Pat::IdentPat(it) => it.name()?.to_string(), 167 ast::Pat::IdentPat(it) => it.name()?.to_string(),
168 _ => return None, 168 _ => return None,
@@ -809,7 +809,7 @@ fn main() {
809 t.method(123); 809 t.method(123);
810 //^^^ param 810 //^^^ param
811 Test::method(&t, 3456); 811 Test::method(&t, 3456);
812 //^^ &self ^^^^ param 812 //^^ self ^^^^ param
813 Test::from_syntax( 813 Test::from_syntax(
814 FileId {}, 814 FileId {},
815 //^^^^^^^^^ file_id 815 //^^^^^^^^^ file_id
@@ -1360,4 +1360,25 @@ fn main() {
1360 "#, 1360 "#,
1361 ); 1361 );
1362 } 1362 }
1363
1364 #[test]
1365 fn self_param_hints() {
1366 check(
1367 r#"
1368struct Foo;
1369
1370impl Foo {
1371 fn foo(self: Self) {}
1372 fn bar(self: &Self) {}
1373}
1374
1375fn main() {
1376 Foo::foo(Foo);
1377 //^^^ self
1378 Foo::bar(&Foo);
1379 //^^^^ self
1380}
1381"#,
1382 )
1383 }
1363} 1384}
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index efcc8ecfe..05bafe9c8 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -179,10 +179,12 @@ pub(crate) fn highlight(
179 element.clone() 179 element.clone()
180 }; 180 };
181 181
182 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 182 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) {
183 let expanded = element_to_highlight.as_token().unwrap().clone(); 183 if token.is_raw() {
184 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { 184 let expanded = element_to_highlight.as_token().unwrap().clone();
185 continue; 185 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
186 continue;
187 }
186 } 188 }
187 } 189 }
188 190
@@ -214,10 +216,6 @@ pub(crate) fn highlight(
214 } 216 }
215 stack.pop_and_inject(None); 217 stack.pop_and_inject(None);
216 } 218 }
217 } else if let Some(string) =
218 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
219 {
220 format_string_highlighter.highlight_format_string(&mut stack, &string, range);
221 } 219 }
222 } 220 }
223 } 221 }
@@ -532,7 +530,7 @@ fn highlight_element(
532 None => h.into(), 530 None => h.into(),
533 } 531 }
534 } 532 }
535 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), 533 STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
536 ATTR => HighlightTag::Attribute.into(), 534 ATTR => HighlightTag::Attribute.into(),
537 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), 535 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
538 BYTE => HighlightTag::ByteLiteral.into(), 536 BYTE => HighlightTag::ByteLiteral.into(),
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 71bde24f0..42f27df5d 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -29,9 +29,7 @@ impl FormatStringHighlighter {
29 .children_with_tokens() 29 .children_with_tokens()
30 .filter(|t| t.kind() != SyntaxKind::WHITESPACE) 30 .filter(|t| t.kind() != SyntaxKind::WHITESPACE)
31 .nth(1) 31 .nth(1)
32 .filter(|e| { 32 .filter(|e| ast::String::can_cast(e.kind()))
33 ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind())
34 })
35 } 33 }
36 _ => {} 34 _ => {}
37 } 35 }
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
index 59a74bc02..e97d1be1a 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -2,7 +2,6 @@
2 2
3use std::{collections::BTreeMap, convert::TryFrom}; 3use std::{collections::BTreeMap, convert::TryFrom};
4 4
5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics; 5use hir::Semantics;
7use ide_db::call_info::ActiveParameter; 6use ide_db::call_info::ActiveParameter;
8use itertools::Itertools; 7use itertools::Itertools;
@@ -15,7 +14,7 @@ use super::HighlightedRangeStack;
15pub(super) fn highlight_injection( 14pub(super) fn highlight_injection(
16 acc: &mut HighlightedRangeStack, 15 acc: &mut HighlightedRangeStack,
17 sema: &Semantics<RootDatabase>, 16 sema: &Semantics<RootDatabase>,
18 literal: ast::RawString, 17 literal: ast::String,
19 expanded: SyntaxToken, 18 expanded: SyntaxToken,
20) -> Option<()> { 19) -> Option<()> {
21 let active_parameter = ActiveParameter::at_token(&sema, expanded)?; 20 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 7941610d6..6dd05c05d 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -1,9 +1,7 @@
1use ide_db::base_db::{FileId, SourceDatabase}; 1use ide_db::base_db::{FileId, SourceDatabase};
2use ide_db::RootDatabase; 2use ide_db::RootDatabase;
3use syntax::{ 3use syntax::{
4 algo, AstNode, NodeOrToken, SourceFile, 4 algo, AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
5 SyntaxKind::{RAW_STRING, STRING},
6 SyntaxToken, TextRange, TextSize,
7}; 5};
8 6
9// Feature: Show Syntax Tree 7// Feature: Show Syntax Tree
@@ -46,7 +44,7 @@ fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<
46 // we'll attempt parsing it as rust syntax 44 // we'll attempt parsing it as rust syntax
47 // to provide the syntax tree of the contents of the string 45 // to provide the syntax tree of the contents of the string
48 match token.kind() { 46 match token.kind() {
49 STRING | RAW_STRING => syntax_tree_for_token(token, text_range), 47 STRING => syntax_tree_for_token(token, text_range),
50 _ => None, 48 _ => None,
51 } 49 }
52} 50}
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 4ab206a83..116b991a8 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -236,10 +236,7 @@ fn abi(p: &mut Parser) {
236 assert!(p.at(T![extern])); 236 assert!(p.at(T![extern]));
237 let abi = p.start(); 237 let abi = p.start();
238 p.bump(T![extern]); 238 p.bump(T![extern]);
239 match p.current() { 239 p.eat(STRING);
240 STRING | RAW_STRING => p.bump_any(),
241 _ => (),
242 }
243 abi.complete(p, ABI); 240 abi.complete(p, ABI);
244} 241}
245 242
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 66a92a4e1..31f42f161 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -15,18 +15,8 @@ use super::*;
15// let _ = b"e"; 15// let _ = b"e";
16// let _ = br"f"; 16// let _ = br"f";
17// } 17// }
18pub(crate) const LITERAL_FIRST: TokenSet = TokenSet::new(&[ 18pub(crate) const LITERAL_FIRST: TokenSet =
19 TRUE_KW, 19 TokenSet::new(&[TRUE_KW, FALSE_KW, INT_NUMBER, FLOAT_NUMBER, BYTE, CHAR, STRING, BYTE_STRING]);
20 FALSE_KW,
21 INT_NUMBER,
22 FLOAT_NUMBER,
23 BYTE,
24 CHAR,
25 STRING,
26 RAW_STRING,
27 BYTE_STRING,
28 RAW_BYTE_STRING,
29]);
30 20
31pub(crate) fn literal(p: &mut Parser) -> Option<CompletedMarker> { 21pub(crate) fn literal(p: &mut Parser) -> Option<CompletedMarker> {
32 if !p.at_ts(LITERAL_FIRST) { 22 if !p.at_ts(LITERAL_FIRST) {
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 22810e6fb..780bc470a 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -239,9 +239,7 @@ fn items_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> {
239 T![static] => consts::static_(p, m), 239 T![static] => consts::static_(p, m),
240 // test extern_block 240 // test extern_block
241 // extern {} 241 // extern {}
242 T![extern] 242 T![extern] if la == T!['{'] || (la == STRING && p.nth(2) == T!['{']) => {
243 if la == T!['{'] || ((la == STRING || la == RAW_STRING) && p.nth(2) == T!['{']) =>
244 {
245 abi(p); 243 abi(p);
246 extern_item_list(p); 244 extern_item_list(p);
247 m.complete(p, EXTERN_BLOCK); 245 m.complete(p, EXTERN_BLOCK);
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 935bd2c5e..8bc6688f3 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -111,9 +111,7 @@ pub enum SyntaxKind {
111 CHAR, 111 CHAR,
112 BYTE, 112 BYTE,
113 STRING, 113 STRING,
114 RAW_STRING,
115 BYTE_STRING, 114 BYTE_STRING,
116 RAW_BYTE_STRING,
117 ERROR, 115 ERROR,
118 IDENT, 116 IDENT,
119 WHITESPACE, 117 WHITESPACE,
@@ -277,8 +275,7 @@ impl SyntaxKind {
277 } 275 }
278 pub fn is_literal(self) -> bool { 276 pub fn is_literal(self) -> bool {
279 match self { 277 match self {
280 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | RAW_STRING | BYTE_STRING 278 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | BYTE_STRING => true,
281 | RAW_BYTE_STRING => true,
282 _ => false, 279 _ => false,
283 } 280 }
284 } 281 }
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index 3fe494729..b0e8863f6 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -114,8 +114,12 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
114 if let Ok(path) = env::var("RUST_SRC_PATH") { 114 if let Ok(path) = env::var("RUST_SRC_PATH") {
115 let path = AbsPathBuf::try_from(path.as_str()) 115 let path = AbsPathBuf::try_from(path.as_str())
116 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; 116 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
117 log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display()); 117 let core = path.join("core");
118 return Ok(path); 118 if core.exists() {
119 log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
120 return Ok(path);
121 }
122 log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
119 } 123 }
120 124
121 let sysroot_path = { 125 let sysroot_path = {
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index f5ba87223..9253c97d0 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -2,7 +2,7 @@
2 2
3use crate::{ 3use crate::{
4 ast::{self, support, AstChildren, AstNode}, 4 ast::{self, support, AstChildren, AstNode},
5 SmolStr, 5 AstToken,
6 SyntaxKind::*, 6 SyntaxKind::*,
7 SyntaxToken, T, 7 SyntaxToken, T,
8}; 8};
@@ -298,12 +298,12 @@ impl ast::ArrayExpr {
298 298
299#[derive(Clone, Debug, PartialEq, Eq, Hash)] 299#[derive(Clone, Debug, PartialEq, Eq, Hash)]
300pub enum LiteralKind { 300pub enum LiteralKind {
301 String, 301 String(ast::String),
302 ByteString, 302 ByteString(ast::ByteString),
303 IntNumber(ast::IntNumber),
304 FloatNumber(ast::FloatNumber),
303 Char, 305 Char,
304 Byte, 306 Byte,
305 IntNumber { suffix: Option<SmolStr> },
306 FloatNumber { suffix: Option<SmolStr> },
307 Bool(bool), 307 Bool(bool),
308} 308}
309 309
@@ -315,44 +315,25 @@ impl ast::Literal {
315 .and_then(|e| e.into_token()) 315 .and_then(|e| e.into_token())
316 .unwrap() 316 .unwrap()
317 } 317 }
318
319 fn find_suffix(text: &str, possible_suffixes: &[&str]) -> Option<SmolStr> {
320 possible_suffixes
321 .iter()
322 .find(|&suffix| text.ends_with(suffix))
323 .map(|&suffix| SmolStr::new(suffix))
324 }
325
326 pub fn kind(&self) -> LiteralKind { 318 pub fn kind(&self) -> LiteralKind {
327 const INT_SUFFIXES: [&str; 12] = [
328 "u64", "u32", "u16", "u8", "usize", "isize", "i64", "i32", "i16", "i8", "u128", "i128",
329 ];
330 const FLOAT_SUFFIXES: [&str; 2] = ["f32", "f64"];
331
332 let token = self.token(); 319 let token = self.token();
333 320
321 if let Some(t) = ast::IntNumber::cast(token.clone()) {
322 return LiteralKind::IntNumber(t);
323 }
324 if let Some(t) = ast::FloatNumber::cast(token.clone()) {
325 return LiteralKind::FloatNumber(t);
326 }
327 if let Some(t) = ast::String::cast(token.clone()) {
328 return LiteralKind::String(t);
329 }
330 if let Some(t) = ast::ByteString::cast(token.clone()) {
331 return LiteralKind::ByteString(t);
332 }
333
334 match token.kind() { 334 match token.kind() {
335 INT_NUMBER => {
336 // FYI: there was a bug here previously, thus the if statement below is necessary.
337 // The lexer treats e.g. `1f64` as an integer literal. See
338 // https://github.com/rust-analyzer/rust-analyzer/issues/1592
339 // and the comments on the linked PR.
340
341 let text = token.text();
342 if let suffix @ Some(_) = Self::find_suffix(&text, &FLOAT_SUFFIXES) {
343 LiteralKind::FloatNumber { suffix }
344 } else {
345 LiteralKind::IntNumber { suffix: Self::find_suffix(&text, &INT_SUFFIXES) }
346 }
347 }
348 FLOAT_NUMBER => {
349 let text = token.text();
350 LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) }
351 }
352 STRING | RAW_STRING => LiteralKind::String,
353 T![true] => LiteralKind::Bool(true), 335 T![true] => LiteralKind::Bool(true),
354 T![false] => LiteralKind::Bool(false), 336 T![false] => LiteralKind::Bool(false),
355 BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString,
356 CHAR => LiteralKind::Char, 337 CHAR => LiteralKind::Char,
357 BYTE => LiteralKind::Byte, 338 BYTE => LiteralKind::Byte,
358 _ => unreachable!(), 339 _ => unreachable!(),
diff --git a/crates/syntax/src/ast/generated/tokens.rs b/crates/syntax/src/ast/generated/tokens.rs
index abadd0b61..728b72cd7 100644
--- a/crates/syntax/src/ast/generated/tokens.rs
+++ b/crates/syntax/src/ast/generated/tokens.rs
@@ -70,16 +70,58 @@ impl AstToken for String {
70} 70}
71 71
72#[derive(Debug, Clone, PartialEq, Eq, Hash)] 72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73pub struct RawString { 73pub struct ByteString {
74 pub(crate) syntax: SyntaxToken, 74 pub(crate) syntax: SyntaxToken,
75} 75}
76impl std::fmt::Display for RawString { 76impl std::fmt::Display for ByteString {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 std::fmt::Display::fmt(&self.syntax, f) 78 std::fmt::Display::fmt(&self.syntax, f)
79 } 79 }
80} 80}
81impl AstToken for RawString { 81impl AstToken for ByteString {
82 fn can_cast(kind: SyntaxKind) -> bool { kind == RAW_STRING } 82 fn can_cast(kind: SyntaxKind) -> bool { kind == BYTE_STRING }
83 fn cast(syntax: SyntaxToken) -> Option<Self> {
84 if Self::can_cast(syntax.kind()) {
85 Some(Self { syntax })
86 } else {
87 None
88 }
89 }
90 fn syntax(&self) -> &SyntaxToken { &self.syntax }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub struct IntNumber {
95 pub(crate) syntax: SyntaxToken,
96}
97impl std::fmt::Display for IntNumber {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 std::fmt::Display::fmt(&self.syntax, f)
100 }
101}
102impl AstToken for IntNumber {
103 fn can_cast(kind: SyntaxKind) -> bool { kind == INT_NUMBER }
104 fn cast(syntax: SyntaxToken) -> Option<Self> {
105 if Self::can_cast(syntax.kind()) {
106 Some(Self { syntax })
107 } else {
108 None
109 }
110 }
111 fn syntax(&self) -> &SyntaxToken { &self.syntax }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Hash)]
115pub struct FloatNumber {
116 pub(crate) syntax: SyntaxToken,
117}
118impl std::fmt::Display for FloatNumber {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 std::fmt::Display::fmt(&self.syntax, f)
121 }
122}
123impl AstToken for FloatNumber {
124 fn can_cast(kind: SyntaxKind) -> bool { kind == FLOAT_NUMBER }
83 fn cast(syntax: SyntaxToken) -> Option<Self> { 125 fn cast(syntax: SyntaxToken) -> Option<Self> {
84 if Self::can_cast(syntax.kind()) { 126 if Self::can_cast(syntax.kind()) {
85 Some(Self { syntax }) 127 Some(Self { syntax })
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 5b06cb767..b1578820f 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -110,8 +110,16 @@ pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::Re
110 } 110 }
111} 111}
112 112
113pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { 113pub fn record_field(
114 ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) 114 visibility: Option<ast::Visibility>,
115 name: ast::Name,
116 ty: ast::Type,
117) -> ast::RecordField {
118 let visibility = match visibility {
119 None => String::new(),
120 Some(it) => format!("{} ", it),
121 };
122 ast_from_text(&format!("struct S {{ {}{}: {}, }}", visibility, name, ty))
115} 123}
116 124
117pub fn block_expr( 125pub fn block_expr(
@@ -351,6 +359,38 @@ pub fn visibility_pub_crate() -> ast::Visibility {
351 ast_from_text("pub(crate) struct S") 359 ast_from_text("pub(crate) struct S")
352} 360}
353 361
362pub fn visibility_pub() -> ast::Visibility {
363 ast_from_text("pub struct S")
364}
365
366pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList {
367 let fields = fields.into_iter().join(", ");
368 ast_from_text(&format!("struct f({});", fields))
369}
370
371pub fn record_field_list(
372 fields: impl IntoIterator<Item = ast::RecordField>,
373) -> ast::RecordFieldList {
374 let fields = fields.into_iter().join(", ");
375 ast_from_text(&format!("struct f {{ {} }}", fields))
376}
377
378pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField {
379 let visibility = match visibility {
380 None => String::new(),
381 Some(it) => format!("{} ", it),
382 };
383 ast_from_text(&format!("struct f({}{});", visibility, ty))
384}
385
386pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant {
387 let field_list = match field_list {
388 None => String::new(),
389 Some(it) => format!("{}", it),
390 };
391 ast_from_text(&format!("enum f {{ {}{} }}", name, field_list))
392}
393
354pub fn fn_( 394pub fn fn_(
355 visibility: Option<ast::Visibility>, 395 visibility: Option<ast::Visibility>,
356 fn_name: ast::Name, 396 fn_name: ast::Name,
@@ -373,6 +413,26 @@ pub fn fn_(
373 )) 413 ))
374} 414}
375 415
416pub fn struct_(
417 visibility: Option<ast::Visibility>,
418 strukt_name: ast::Name,
419 type_params: Option<ast::GenericParamList>,
420 field_list: ast::FieldList,
421) -> ast::Struct {
422 let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" };
423 let type_params =
424 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
425 let visibility = match visibility {
426 None => String::new(),
427 Some(it) => format!("{} ", it),
428 };
429
430 ast_from_text(&format!(
431 "{}struct {}{}{}{}",
432 visibility, strukt_name, type_params, field_list, semicolon
433 ))
434}
435
376fn ast_from_text<N: AstNode>(text: &str) -> N { 436fn ast_from_text<N: AstNode>(text: &str) -> N {
377 let parse = SourceFile::parse(text); 437 let parse = SourceFile::parse(text);
378 let node = match parse.tree().syntax().descendants().find_map(N::cast) { 438 let node = match parse.tree().syntax().descendants().find_map(N::cast) {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index c5cd1c504..ce35ac01a 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -7,7 +7,7 @@ use itertools::Itertools;
7use parser::SyntaxKind; 7use parser::SyntaxKind;
8 8
9use crate::{ 9use crate::{
10 ast::{self, support, token_ext::HasStringValue, AstNode, AstToken, NameOwner, SyntaxNode}, 10 ast::{self, support, AstNode, AstToken, NameOwner, SyntaxNode},
11 SmolStr, SyntaxElement, SyntaxToken, T, 11 SmolStr, SyntaxElement, SyntaxToken, T,
12}; 12};
13 13
@@ -55,13 +55,7 @@ impl ast::Attr {
55 let key = self.simple_name()?; 55 let key = self.simple_name()?;
56 let value_token = lit.syntax().first_token()?; 56 let value_token = lit.syntax().first_token()?;
57 57
58 let value: SmolStr = if let Some(s) = ast::String::cast(value_token.clone()) { 58 let value: SmolStr = ast::String::cast(value_token.clone())?.value()?.into();
59 s.value()?.into()
60 } else if let Some(s) = ast::RawString::cast(value_token) {
61 s.value()?.into()
62 } else {
63 return None;
64 };
65 59
66 Some((key, value)) 60 Some((key, value))
67 } 61 }
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index c5ef92733..e4e512f2e 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -8,11 +8,11 @@ use std::{
8use rustc_lexer::unescape::{unescape_literal, Mode}; 8use rustc_lexer::unescape::{unescape_literal, Mode};
9 9
10use crate::{ 10use crate::{
11 ast::{AstToken, Comment, RawString, String, Whitespace}, 11 ast::{self, AstToken},
12 TextRange, TextSize, 12 TextRange, TextSize,
13}; 13};
14 14
15impl Comment { 15impl ast::Comment {
16 pub fn kind(&self) -> CommentKind { 16 pub fn kind(&self) -> CommentKind {
17 kind_by_prefix(self.text()) 17 kind_by_prefix(self.text())
18 } 18 }
@@ -80,7 +80,7 @@ fn kind_by_prefix(text: &str) -> CommentKind {
80 panic!("bad comment text: {:?}", text) 80 panic!("bad comment text: {:?}", text)
81} 81}
82 82
83impl Whitespace { 83impl ast::Whitespace {
84 pub fn spans_multiple_lines(&self) -> bool { 84 pub fn spans_multiple_lines(&self) -> bool {
85 let text = self.text(); 85 let text = self.text();
86 text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) 86 text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
@@ -114,43 +114,28 @@ impl QuoteOffsets {
114 } 114 }
115} 115}
116 116
117pub trait HasQuotes: AstToken { 117impl ast::String {
118 fn quote_offsets(&self) -> Option<QuoteOffsets> { 118 pub fn is_raw(&self) -> bool {
119 let text = self.text().as_str(); 119 self.text().starts_with('r')
120 let offsets = QuoteOffsets::new(text)?;
121 let o = self.syntax().text_range().start();
122 let offsets = QuoteOffsets {
123 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
124 contents: offsets.contents + o,
125 };
126 Some(offsets)
127 }
128 fn open_quote_text_range(&self) -> Option<TextRange> {
129 self.quote_offsets().map(|it| it.quotes.0)
130 } 120 }
131 121 pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
132 fn close_quote_text_range(&self) -> Option<TextRange> { 122 let contents_range = self.text_range_between_quotes()?;
133 self.quote_offsets().map(|it| it.quotes.1) 123 assert!(TextRange::up_to(contents_range.len()).contains_range(range));
124 Some(range + contents_range.start())
134 } 125 }
135 126
136 fn text_range_between_quotes(&self) -> Option<TextRange> { 127 pub fn value(&self) -> Option<Cow<'_, str>> {
137 self.quote_offsets().map(|it| it.contents) 128 if self.is_raw() {
138 } 129 let text = self.text().as_str();
139} 130 let text =
140 131 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
141impl HasQuotes for String {} 132 return Some(Cow::Borrowed(text));
142impl HasQuotes for RawString {} 133 }
143
144pub trait HasStringValue: HasQuotes {
145 fn value(&self) -> Option<Cow<'_, str>>;
146}
147 134
148impl HasStringValue for String {
149 fn value(&self) -> Option<Cow<'_, str>> {
150 let text = self.text().as_str(); 135 let text = self.text().as_str();
151 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; 136 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
152 137
153 let mut buf = std::string::String::with_capacity(text.len()); 138 let mut buf = String::with_capacity(text.len());
154 let mut has_error = false; 139 let mut has_error = false;
155 unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char { 140 unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char {
156 Ok(c) => buf.push(c), 141 Ok(c) => buf.push(c),
@@ -164,21 +149,31 @@ impl HasStringValue for String {
164 let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) }; 149 let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) };
165 Some(res) 150 Some(res)
166 } 151 }
167}
168 152
169impl HasStringValue for RawString { 153 pub fn quote_offsets(&self) -> Option<QuoteOffsets> {
170 fn value(&self) -> Option<Cow<'_, str>> {
171 let text = self.text().as_str(); 154 let text = self.text().as_str();
172 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; 155 let offsets = QuoteOffsets::new(text)?;
173 Some(Cow::Borrowed(text)) 156 let o = self.syntax().text_range().start();
157 let offsets = QuoteOffsets {
158 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
159 contents: offsets.contents + o,
160 };
161 Some(offsets)
162 }
163 pub fn text_range_between_quotes(&self) -> Option<TextRange> {
164 self.quote_offsets().map(|it| it.contents)
165 }
166 pub fn open_quote_text_range(&self) -> Option<TextRange> {
167 self.quote_offsets().map(|it| it.quotes.0)
168 }
169 pub fn close_quote_text_range(&self) -> Option<TextRange> {
170 self.quote_offsets().map(|it| it.quotes.1)
174 } 171 }
175} 172}
176 173
177impl RawString { 174impl ast::ByteString {
178 pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { 175 pub fn is_raw(&self) -> bool {
179 let contents_range = self.text_range_between_quotes()?; 176 self.text().starts_with("br")
180 assert!(TextRange::up_to(contents_range.len()).contains_range(range));
181 Some(range + contents_range.start())
182 } 177 }
183} 178}
184 179
@@ -500,7 +495,7 @@ pub trait HasFormatSpecifier: AstToken {
500 } 495 }
501} 496}
502 497
503impl HasFormatSpecifier for String { 498impl HasFormatSpecifier for ast::String {
504 fn char_ranges( 499 fn char_ranges(
505 &self, 500 &self,
506 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { 501 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
@@ -521,18 +516,86 @@ impl HasFormatSpecifier for String {
521 } 516 }
522} 517}
523 518
524impl HasFormatSpecifier for RawString { 519impl ast::IntNumber {
525 fn char_ranges( 520 const SUFFIXES: &'static [&'static str] = &[
526 &self, 521 "u8", "u16", "u32", "u64", "u128", "usize", // Unsigned.
527 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { 522 "i8", "i16", "i32", "i64", "i128", "isize", // Signed.
528 let text = self.text().as_str(); 523 ];
529 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; 524
530 let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); 525 pub fn radix(&self) -> Radix {
526 match self.text().get(..2).unwrap_or_default() {
527 "0b" => Radix::Binary,
528 "0o" => Radix::Octal,
529 "0x" => Radix::Hexadecimal,
530 _ => Radix::Decimal,
531 }
532 }
531 533
532 let mut res = Vec::with_capacity(text.len()); 534 pub fn value(&self) -> Option<u128> {
533 for (idx, c) in text.char_indices() { 535 let token = self.syntax();
534 res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c))); 536
537 let mut text = token.text().as_str();
538 if let Some(suffix) = self.suffix() {
539 text = &text[..text.len() - suffix.len()]
540 }
541
542 let radix = self.radix();
543 text = &text[radix.prefix_len()..];
544
545 let buf;
546 if text.contains("_") {
547 buf = text.replace('_', "");
548 text = buf.as_str();
549 };
550
551 let value = u128::from_str_radix(text, radix as u32).ok()?;
552 Some(value)
553 }
554
555 pub fn suffix(&self) -> Option<&str> {
556 let text = self.text();
557 // FIXME: don't check a fixed set of suffixes, `1_0_1_l_o_l` is valid
558 // syntax, suffix is `l_o_l`.
559 ast::IntNumber::SUFFIXES.iter().chain(ast::FloatNumber::SUFFIXES.iter()).find_map(
560 |suffix| {
561 if text.ends_with(suffix) {
562 return Some(&text[text.len() - suffix.len()..]);
563 }
564 None
565 },
566 )
567 }
568}
569
570impl ast::FloatNumber {
571 const SUFFIXES: &'static [&'static str] = &["f32", "f64"];
572 pub fn suffix(&self) -> Option<&str> {
573 let text = self.text();
574 ast::FloatNumber::SUFFIXES.iter().find_map(|suffix| {
575 if text.ends_with(suffix) {
576 return Some(&text[text.len() - suffix.len()..]);
577 }
578 None
579 })
580 }
581}
582
583#[derive(Debug, PartialEq, Eq, Copy, Clone)]
584pub enum Radix {
585 Binary = 2,
586 Octal = 8,
587 Decimal = 10,
588 Hexadecimal = 16,
589}
590
591impl Radix {
592 pub const ALL: &'static [Radix] =
593 &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal];
594
595 const fn prefix_len(&self) -> usize {
596 match self {
597 Self::Decimal => 0,
598 _ => 2,
535 } 599 }
536 Some(res)
537 } 600 }
538} 601}
diff --git a/crates/syntax/src/parsing/lexer.rs b/crates/syntax/src/parsing/lexer.rs
index 7e38c32cc..8afd7e53b 100644
--- a/crates/syntax/src/parsing/lexer.rs
+++ b/crates/syntax/src/parsing/lexer.rs
@@ -3,7 +3,7 @@
3 3
4use std::convert::TryInto; 4use std::convert::TryInto;
5 5
6use rustc_lexer::{LiteralKind as LK, RawStrError}; 6use rustc_lexer::RawStrError;
7 7
8use crate::{ 8use crate::{
9 SyntaxError, 9 SyntaxError,
@@ -185,63 +185,77 @@ fn rustc_token_kind_to_syntax_kind(
185 return (syntax_kind, None); 185 return (syntax_kind, None);
186 186
187 fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<&'static str>) { 187 fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<&'static str>) {
188 #[rustfmt::skip] 188 let mut err = "";
189 let syntax_kind = match *kind { 189 let syntax_kind = match *kind {
190 LK::Int { empty_int: false, .. } => INT_NUMBER, 190 rustc_lexer::LiteralKind::Int { empty_int, base: _ } => {
191 LK::Int { empty_int: true, .. } => { 191 if empty_int {
192 return (INT_NUMBER, Some("Missing digits after the integer base prefix")) 192 err = "Missing digits after the integer base prefix";
193 }
194 INT_NUMBER
193 } 195 }
194 196 rustc_lexer::LiteralKind::Float { empty_exponent, base: _ } => {
195 LK::Float { empty_exponent: false, .. } => FLOAT_NUMBER, 197 if empty_exponent {
196 LK::Float { empty_exponent: true, .. } => { 198 err = "Missing digits after the exponent symbol";
197 return (FLOAT_NUMBER, Some("Missing digits after the exponent symbol")) 199 }
200 FLOAT_NUMBER
198 } 201 }
199 202 rustc_lexer::LiteralKind::Char { terminated } => {
200 LK::Char { terminated: true } => CHAR, 203 if !terminated {
201 LK::Char { terminated: false } => { 204 err = "Missing trailing `'` symbol to terminate the character literal";
202 return (CHAR, Some("Missing trailing `'` symbol to terminate the character literal")) 205 }
206 CHAR
203 } 207 }
204 208 rustc_lexer::LiteralKind::Byte { terminated } => {
205 LK::Byte { terminated: true } => BYTE, 209 if !terminated {
206 LK::Byte { terminated: false } => { 210 err = "Missing trailing `'` symbol to terminate the byte literal";
207 return (BYTE, Some("Missing trailing `'` symbol to terminate the byte literal")) 211 }
212 BYTE
208 } 213 }
209 214 rustc_lexer::LiteralKind::Str { terminated } => {
210 LK::Str { terminated: true } => STRING, 215 if !terminated {
211 LK::Str { terminated: false } => { 216 err = "Missing trailing `\"` symbol to terminate the string literal";
212 return (STRING, Some("Missing trailing `\"` symbol to terminate the string literal")) 217 }
218 STRING
213 } 219 }
214 220 rustc_lexer::LiteralKind::ByteStr { terminated } => {
215 221 if !terminated {
216 LK::ByteStr { terminated: true } => BYTE_STRING, 222 err = "Missing trailing `\"` symbol to terminate the byte string literal";
217 LK::ByteStr { terminated: false } => { 223 }
218 return (BYTE_STRING, Some("Missing trailing `\"` symbol to terminate the byte string literal")) 224 BYTE_STRING
225 }
226 rustc_lexer::LiteralKind::RawStr { err: raw_str_err, .. } => {
227 if let Some(raw_str_err) = raw_str_err {
228 err = match raw_str_err {
229 RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw string literal",
230 RawStrError::NoTerminator { expected, found, .. } => if expected == found {
231 "Missing trailing `\"` to terminate the raw string literal"
232 } else {
233 "Missing trailing `\"` with `#` symbols to terminate the raw string literal"
234 },
235 RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols",
236 };
237 };
238 STRING
239 }
240 rustc_lexer::LiteralKind::RawByteStr { err: raw_str_err, .. } => {
241 if let Some(raw_str_err) = raw_str_err {
242 err = match raw_str_err {
243 RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw byte string literal",
244 RawStrError::NoTerminator { expected, found, .. } => if expected == found {
245 "Missing trailing `\"` to terminate the raw byte string literal"
246 } else {
247 "Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"
248 },
249 RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols",
250 };
251 };
252
253 BYTE_STRING
219 } 254 }
220
221 LK::RawStr { err, .. } => match err {
222 None => RAW_STRING,
223 Some(RawStrError::InvalidStarter { .. }) => return (RAW_STRING, Some("Missing `\"` symbol after `#` symbols to begin the raw string literal")),
224 Some(RawStrError::NoTerminator { expected, found, .. }) => if expected == found {
225 return (RAW_STRING, Some("Missing trailing `\"` to terminate the raw string literal"))
226 } else {
227 return (RAW_STRING, Some("Missing trailing `\"` with `#` symbols to terminate the raw string literal"))
228
229 },
230 Some(RawStrError::TooManyDelimiters { .. }) => return (RAW_STRING, Some("Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols")),
231 },
232 LK::RawByteStr { err, .. } => match err {
233 None => RAW_BYTE_STRING,
234 Some(RawStrError::InvalidStarter { .. }) => return (RAW_BYTE_STRING, Some("Missing `\"` symbol after `#` symbols to begin the raw byte string literal")),
235 Some(RawStrError::NoTerminator { expected, found, .. }) => if expected == found {
236 return (RAW_BYTE_STRING, Some("Missing trailing `\"` to terminate the raw byte string literal"))
237 } else {
238 return (RAW_BYTE_STRING, Some("Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"))
239
240 },
241 Some(RawStrError::TooManyDelimiters { .. }) => return (RAW_BYTE_STRING, Some("Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols")),
242 },
243 }; 255 };
244 256
245 (syntax_kind, None) 257 let err = if err.is_empty() { None } else { Some(err) };
258
259 (syntax_kind, err)
246 } 260 }
247} 261}
diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs
index 4149f856a..190f5f67a 100644
--- a/crates/syntax/src/parsing/reparsing.rs
+++ b/crates/syntax/src/parsing/reparsing.rs
@@ -44,7 +44,7 @@ fn reparse_token<'node>(
44 let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone(); 44 let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
45 let prev_token_kind = prev_token.kind(); 45 let prev_token_kind = prev_token.kind();
46 match prev_token_kind { 46 match prev_token_kind {
47 WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => { 47 WHITESPACE | COMMENT | IDENT | STRING => {
48 if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT { 48 if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT {
49 // removing a new line may extends previous token 49 // removing a new line may extends previous token
50 let deleted_range = edit.delete - prev_token.text_range().start(); 50 let deleted_range = edit.delete - prev_token.text_range().start();
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index 0f9a5e8ae..6f45149bf 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -4,7 +4,7 @@ mod block;
4 4
5use crate::{ 5use crate::{
6 algo, ast, match_ast, AstNode, SyntaxError, 6 algo, ast, match_ast, AstNode, SyntaxError,
7 SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST, FN, INT_NUMBER, STRING, TYPE_ALIAS}, 7 SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
8 SyntaxNode, SyntaxToken, TextSize, T, 8 SyntaxNode, SyntaxToken, TextSize, T,
9}; 9};
10use rowan::Direction; 10use rowan::Direction;
@@ -121,36 +121,42 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
121 acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off)); 121 acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off));
122 }; 122 };
123 123
124 match token.kind() { 124 match literal.kind() {
125 BYTE => { 125 ast::LiteralKind::String(s) => {
126 if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) { 126 if !s.is_raw() {
127 push_err(2, e); 127 if let Some(without_quotes) = unquote(text, 1, '"') {
128 unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
129 if let Err(err) = char {
130 push_err(1, (range.start, err));
131 }
132 })
133 }
128 } 134 }
129 } 135 }
130 CHAR => { 136 ast::LiteralKind::ByteString(s) => {
131 if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) { 137 if !s.is_raw() {
132 push_err(1, e); 138 if let Some(without_quotes) = unquote(text, 2, '"') {
139 unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
140 if let Err(err) = char {
141 push_err(2, (range.start, err));
142 }
143 })
144 }
133 } 145 }
134 } 146 }
135 BYTE_STRING => { 147 ast::LiteralKind::Char => {
136 if let Some(without_quotes) = unquote(text, 2, '"') { 148 if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) {
137 unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| { 149 push_err(1, e);
138 if let Err(err) = char {
139 push_err(2, (range.start, err));
140 }
141 })
142 } 150 }
143 } 151 }
144 STRING => { 152 ast::LiteralKind::Byte => {
145 if let Some(without_quotes) = unquote(text, 1, '"') { 153 if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
146 unescape_literal(without_quotes, Mode::Str, &mut |range, char| { 154 push_err(2, e);
147 if let Err(err) = char {
148 push_err(1, (range.start, err));
149 }
150 })
151 } 155 }
152 } 156 }
153 _ => (), 157 ast::LiteralKind::IntNumber(_)
158 | ast::LiteralKind::FloatNumber(_)
159 | ast::LiteralKind::Bool(_) => {}
154 } 160 }
155} 161}
156 162
diff --git a/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
index 6fd59ccc0..54e707b73 100644
--- a/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
@@ -1,2 +1,2 @@
1RAW_STRING 4 "r##\"" 1STRING 4 "r##\""
2> error0..4 token("r##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..4 token("r##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt b/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
index 8d9ca0e8f..1f9889775 100644
--- a/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
+++ b/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
@@ -1,2 +1,2 @@
1RAW_STRING 8 "r##\"🦀" 1STRING 8 "r##\"🦀"
2> error0..8 token("r##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..8 token("r##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt b/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
index a906380c7..93f6f72ae 100644
--- a/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
@@ -1,2 +1,2 @@
1RAW_STRING 8 "r##\"\\x7f" 1STRING 8 "r##\"\\x7f"
2> error0..8 token("r##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..8 token("r##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt b/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
index 5667c6149..1d2ebc60f 100644
--- a/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
@@ -1,2 +1,2 @@
1RAW_STRING 12 "r##\"\\u{20AA}" 1STRING 12 "r##\"\\u{20AA}"
2> error0..12 token("r##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..12 token("r##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt b/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
index 141c8268e..c567ab7e2 100644
--- a/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
+++ b/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
@@ -1,2 +1,2 @@
1RAW_STRING 5 "r##\" " 1STRING 5 "r##\" "
2> error0..5 token("r##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..5 token("r##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt b/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
index f61d4cc91..343b20323 100644
--- a/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
+++ b/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
@@ -1,2 +1,2 @@
1RAW_STRING 5 "r##\"\\" 1STRING 5 "r##\"\\"
2> error0..5 token("r##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..5 token("r##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt b/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
index 12e2c0fc0..041a42737 100644
--- a/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
+++ b/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
@@ -1,2 +1,2 @@
1RAW_STRING 6 "r##\"\\n" 1STRING 6 "r##\"\\n"
2> error0..6 token("r##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal) 2> error0..6 token("r##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
index fe12cb5fc..efaa1cafd 100644
--- a/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 5 "br##\"" 1BYTE_STRING 5 "br##\""
2> error0..5 token("br##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..5 token("br##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt b/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
index 5be2a7861..b6c938f94 100644
--- a/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
+++ b/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 9 "br##\"🦀" 1BYTE_STRING 9 "br##\"🦀"
2> error0..9 token("br##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..9 token("br##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt b/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
index 6cbe08d07..f82efe49a 100644
--- a/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 9 "br##\"\\x7f" 1BYTE_STRING 9 "br##\"\\x7f"
2> error0..9 token("br##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..9 token("br##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt b/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
index f56a4f984..4e4a57696 100644
--- a/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 13 "br##\"\\u{20AA}" 1BYTE_STRING 13 "br##\"\\u{20AA}"
2> error0..13 token("br##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..13 token("br##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt b/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
index 3d32ce34e..0018c8623 100644
--- a/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
+++ b/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 6 "br##\" " 1BYTE_STRING 6 "br##\" "
2> error0..6 token("br##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..6 token("br##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt b/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
index 320fea177..c3ba4ae82 100644
--- a/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
+++ b/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 6 "br##\"\\" 1BYTE_STRING 6 "br##\"\\"
2> error0..6 token("br##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..6 token("br##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt b/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
index b3a56380c..7bda72276 100644
--- a/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
+++ b/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 7 "br##\"\\n" 1BYTE_STRING 7 "br##\"\\n"
2> error0..7 token("br##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal) 2> error0..7 token("br##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
index 5af1e2d97..ce92d2ff7 100644
--- a/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
@@ -1,2 +1,2 @@
1RAW_STRING 3 "r##" 1STRING 3 "r##"
2> error0..3 token("r##") msg(Missing `"` symbol after `#` symbols to begin the raw string literal) 2> error0..3 token("r##") msg(Missing `"` symbol after `#` symbols to begin the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
index aec7afd92..a75d9030c 100644
--- a/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
@@ -1,2 +1,2 @@
1RAW_BYTE_STRING 4 "br##" 1BYTE_STRING 4 "br##"
2> error0..4 token("br##") msg(Missing `"` symbol after `#` symbols to begin the raw byte string literal) 2> error0..4 token("br##") msg(Missing `"` symbol after `#` symbols to begin the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt b/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
index e22fe5374..516e0b78e 100644
--- a/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
+++ b/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
@@ -1,4 +1,4 @@
1RAW_STRING 4 "r## " 1STRING 4 "r## "
2IDENT 1 "I" 2IDENT 1 "I"
3WHITESPACE 1 " " 3WHITESPACE 1 " "
4IDENT 4 "lack" 4IDENT 4 "lack"
diff --git a/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt b/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
index d74ea4c27..2f8a6f5f2 100644
--- a/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
+++ b/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
@@ -1,4 +1,4 @@
1RAW_BYTE_STRING 5 "br## " 1BYTE_STRING 5 "br## "
2IDENT 1 "I" 2IDENT 1 "I"
3WHITESPACE 1 " " 3WHITESPACE 1 " "
4IDENT 4 "lack" 4IDENT 4 "lack"
diff --git a/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt b/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
index bc03b51a8..e61ad99be 100644
--- a/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
@@ -4,13 +4,13 @@ BYTE 4 "b\'x\'"
4WHITESPACE 1 " " 4WHITESPACE 1 " "
5BYTE_STRING 6 "b\"foo\"" 5BYTE_STRING 6 "b\"foo\""
6WHITESPACE 1 " " 6WHITESPACE 1 " "
7RAW_BYTE_STRING 4 "br\"\"" 7BYTE_STRING 4 "br\"\""
8WHITESPACE 1 "\n" 8WHITESPACE 1 "\n"
9BYTE 6 "b\'\'suf" 9BYTE 6 "b\'\'suf"
10WHITESPACE 1 " " 10WHITESPACE 1 " "
11BYTE_STRING 5 "b\"\"ix" 11BYTE_STRING 5 "b\"\"ix"
12WHITESPACE 1 " " 12WHITESPACE 1 " "
13RAW_BYTE_STRING 6 "br\"\"br" 13BYTE_STRING 6 "br\"\"br"
14WHITESPACE 1 "\n" 14WHITESPACE 1 "\n"
15BYTE 5 "b\'\\n\'" 15BYTE 5 "b\'\\n\'"
16WHITESPACE 1 " " 16WHITESPACE 1 " "
diff --git a/crates/syntax/test_data/lexer/ok/0009_strings.txt b/crates/syntax/test_data/lexer/ok/0009_strings.txt
index 4cb4d711d..988a8877b 100644
--- a/crates/syntax/test_data/lexer/ok/0009_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0009_strings.txt
@@ -1,6 +1,6 @@
1STRING 7 "\"hello\"" 1STRING 7 "\"hello\""
2WHITESPACE 1 " " 2WHITESPACE 1 " "
3RAW_STRING 8 "r\"world\"" 3STRING 8 "r\"world\""
4WHITESPACE 1 " " 4WHITESPACE 1 " "
5STRING 17 "\"\\n\\\"\\\\no escape\"" 5STRING 17 "\"\\n\\\"\\\\no escape\""
6WHITESPACE 1 " " 6WHITESPACE 1 " "
diff --git a/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt b/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
index 9cf0957d1..db0d5ffd1 100644
--- a/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
@@ -1,2 +1,2 @@
1RAW_STRING 36 "r###\"this is a r##\"raw\"## string\"###" 1STRING 36 "r###\"this is a r##\"raw\"## string\"###"
2WHITESPACE 1 "\n" 2WHITESPACE 1 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast b/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
index 9a87b5b93..ae838105d 100644
--- a/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
@@ -104,7 +104,7 @@ [email protected]
104 [email protected] "=" 104 [email protected] "="
105 [email protected] " " 105 [email protected] " "
106 [email protected] 106 [email protected]
107 RAW_[email protected] "r\"d\"" 107 [email protected] "r\"d\""
108 [email protected] ";" 108 [email protected] ";"
109 [email protected] "\n " 109 [email protected] "\n "
110 [email protected] 110 [email protected]
@@ -128,7 +128,7 @@ [email protected]
128 [email protected] "=" 128 [email protected] "="
129 [email protected] " " 129 [email protected] " "
130 [email protected] 130 [email protected]
131 RAW_[email protected] "br\"f\"" 131 [email protected] "br\"f\""
132 [email protected] ";" 132 [email protected] ";"
133 [email protected] "\n" 133 [email protected] "\n"
134 [email protected] "}" 134 [email protected] "}"