aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/paths/src/lib.rs74
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs2
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs2
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs2
-rw-r--r--crates/ra_db/src/fixture.rs10
-rw-r--r--crates/ra_hir/src/source_analyzer.rs30
-rw-r--r--crates/ra_hir_ty/src/tests/method_resolution.rs2
-rw-r--r--crates/ra_ide/src/diagnostics.rs3
-rw-r--r--crates/ra_ide/src/goto_definition.rs80
-rw-r--r--crates/ra_ide/src/mock_analysis.rs10
-rw-r--r--crates/ra_ide/src/ssr.rs1
-rw-r--r--crates/ra_ssr/src/lib.rs18
-rw-r--r--crates/ra_ssr/src/matching.rs105
-rw-r--r--crates/ra_ssr/src/replacing.rs10
-rw-r--r--crates/ra_ssr/src/tests.rs53
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs54
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs2
-rw-r--r--crates/test_utils/src/lib.rs10
18 files changed, 408 insertions, 60 deletions
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
index 190c50913..32267f2e6 100644
--- a/crates/paths/src/lib.rs
+++ b/crates/paths/src/lib.rs
@@ -95,6 +95,80 @@ impl AbsPath {
95 pub fn normalize(&self) -> AbsPathBuf { 95 pub fn normalize(&self) -> AbsPathBuf {
96 AbsPathBuf(normalize_path(&self.0)) 96 AbsPathBuf(normalize_path(&self.0))
97 } 97 }
98 pub fn to_path_buf(&self) -> AbsPathBuf {
99 AbsPathBuf::try_from(self.0.to_path_buf()).unwrap()
100 }
101 pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> {
102 self.0.strip_prefix(base).ok().map(RelPath::new_unchecked)
103 }
104}
105
106#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
107pub struct RelPathBuf(PathBuf);
108
109impl From<RelPathBuf> for PathBuf {
110 fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf {
111 path_buf
112 }
113}
114
115impl ops::Deref for RelPathBuf {
116 type Target = RelPath;
117 fn deref(&self) -> &RelPath {
118 self.as_path()
119 }
120}
121
122impl AsRef<Path> for RelPathBuf {
123 fn as_ref(&self) -> &Path {
124 self.0.as_path()
125 }
126}
127
128impl TryFrom<PathBuf> for RelPathBuf {
129 type Error = PathBuf;
130 fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> {
131 if !path_buf.is_relative() {
132 return Err(path_buf);
133 }
134 Ok(RelPathBuf(path_buf))
135 }
136}
137
138impl TryFrom<&str> for RelPathBuf {
139 type Error = PathBuf;
140 fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> {
141 RelPathBuf::try_from(PathBuf::from(path))
142 }
143}
144
145impl RelPathBuf {
146 pub fn as_path(&self) -> &RelPath {
147 RelPath::new_unchecked(self.0.as_path())
148 }
149}
150
151#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
152#[repr(transparent)]
153pub struct RelPath(Path);
154
155impl ops::Deref for RelPath {
156 type Target = Path;
157 fn deref(&self) -> &Path {
158 &self.0
159 }
160}
161
162impl AsRef<Path> for RelPath {
163 fn as_ref(&self) -> &Path {
164 &self.0
165 }
166}
167
168impl RelPath {
169 pub fn new_unchecked(path: &Path) -> &RelPath {
170 unsafe { &*(path as *const Path as *const RelPath) }
171 }
98} 172}
99 173
100// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85 174// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index 776bddf91..b0e56e1b5 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -128,7 +128,7 @@ impl From<foo::bar::baz::Boo> for A {
128 128
129 fn check_not_applicable(ra_fixture: &str) { 129 fn check_not_applicable(ra_fixture: &str) {
130 let fixture = 130 let fixture =
131 format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); 131 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
132 check_assist_not_applicable(add_from_impl_for_enum, &fixture) 132 check_assist_not_applicable(add_from_impl_for_enum, &fixture)
133 } 133 }
134 134
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
index 44db7917a..43b4584b4 100644
--- a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -301,7 +301,7 @@ fn another_fn() {
301 301
302 fn check_not_applicable(ra_fixture: &str) { 302 fn check_not_applicable(ra_fixture: &str) {
303 let fixture = 303 let fixture =
304 format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); 304 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
305 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) 305 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
306 } 306 }
307 307
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 569efb768..3c12c1daa 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -765,7 +765,7 @@ fn foo(opt: Option<i32>) {
765 } 765 }
766}"#; 766}"#;
767 let before = 767 let before =
768 &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); 768 &format!("//- /main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE);
769 769
770 check_assist( 770 check_assist(
771 fill_match_arms, 771 fill_match_arms,
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 482a2f3e6..af8fe11ec 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -164,7 +164,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
164 164
165 let mut source_root = SourceRoot::new_local(); 165 let mut source_root = SourceRoot::new_local();
166 let mut source_root_id = WORKSPACE; 166 let mut source_root_id = WORKSPACE;
167 let mut source_root_prefix: RelativePathBuf = "/".into(); 167 let mut source_root_prefix = "/".to_string();
168 let mut file_id = FileId(0); 168 let mut file_id = FileId(0);
169 169
170 let mut file_position = None; 170 let mut file_position = None;
@@ -212,9 +212,9 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
212 }; 212 };
213 213
214 db.set_file_text(file_id, Arc::new(text)); 214 db.set_file_text(file_id, Arc::new(text));
215 db.set_file_relative_path(file_id, meta.path.clone()); 215 db.set_file_relative_path(file_id, meta.path.clone().into());
216 db.set_file_source_root(file_id, source_root_id); 216 db.set_file_source_root(file_id, source_root_id);
217 source_root.insert_file(meta.path, file_id); 217 source_root.insert_file(meta.path.into(), file_id);
218 218
219 file_id.0 += 1; 219 file_id.0 += 1;
220 } 220 }
@@ -245,12 +245,12 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
245} 245}
246 246
247enum ParsedMeta { 247enum ParsedMeta {
248 Root { path: RelativePathBuf }, 248 Root { path: String },
249 File(FileMeta), 249 File(FileMeta),
250} 250}
251 251
252struct FileMeta { 252struct FileMeta {
253 path: RelativePathBuf, 253 path: String,
254 krate: Option<String>, 254 krate: Option<String>,
255 deps: Vec<String>, 255 deps: Vec<String>,
256 cfg: CfgOptions, 256 cfg: CfgOptions,
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 757d1e397..1d6c47103 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -216,13 +216,43 @@ impl SourceAnalyzer {
216 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { 216 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) {
217 return Some(PathResolution::AssocItem(assoc.into())); 217 return Some(PathResolution::AssocItem(assoc.into()));
218 } 218 }
219 if let Some(VariantId::EnumVariantId(variant)) =
220 self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
221 {
222 return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
223 }
219 } 224 }
225
220 if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { 226 if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) {
221 let pat_id = self.pat_id(&path_pat.into())?; 227 let pat_id = self.pat_id(&path_pat.into())?;
222 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { 228 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
223 return Some(PathResolution::AssocItem(assoc.into())); 229 return Some(PathResolution::AssocItem(assoc.into()));
224 } 230 }
231 if let Some(VariantId::EnumVariantId(variant)) =
232 self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
233 {
234 return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
235 }
236 }
237
238 if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordLit::cast) {
239 let expr_id = self.expr_id(db, &rec_lit.into())?;
240 if let Some(VariantId::EnumVariantId(variant)) =
241 self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
242 {
243 return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
244 }
225 } 245 }
246
247 if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) {
248 let pat_id = self.pat_id(&rec_pat.into())?;
249 if let Some(VariantId::EnumVariantId(variant)) =
250 self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
251 {
252 return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
253 }
254 }
255
226 // This must be a normal source file rather than macro file. 256 // This must be a normal source file rather than macro file.
227 let hir_path = 257 let hir_path =
228 crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?; 258 crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?;
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs
index 804297315..a98efb1cc 100644
--- a/crates/ra_hir_ty/src/tests/method_resolution.rs
+++ b/crates/ra_hir_ty/src/tests/method_resolution.rs
@@ -933,7 +933,7 @@ fn method_resolution_overloaded_method() {
933 test_utils::mark::check!(impl_self_type_match_without_receiver); 933 test_utils::mark::check!(impl_self_type_match_without_receiver);
934 let t = type_at( 934 let t = type_at(
935 r#" 935 r#"
936//- main.rs 936//- /main.rs
937struct Wrapper<T>(T); 937struct Wrapper<T>(T);
938struct Foo<T>(T); 938struct Foo<T>(T);
939struct Bar<T>(T); 939struct Bar<T>(T);
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index a88a978d7..9bde1db8e 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -167,6 +167,9 @@ fn missing_struct_field_fix(
167 }; 167 };
168 168
169 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; 169 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
170 if new_field_type.is_unknown() {
171 return None;
172 }
170 let new_field = make::record_field_def( 173 let new_field = make::record_field_def(
171 record_expr.field_name()?, 174 record_expr.field_name()?,
172 make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), 175 make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 0798d2c36..450ce0ba7 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -908,4 +908,84 @@ mod tests {
908 "x: i32|x", 908 "x: i32|x",
909 ); 909 );
910 } 910 }
911
912 #[test]
913 fn goto_def_for_enum_variant_self_pattern_const() {
914 check_goto(
915 "
916 //- /lib.rs
917 enum Foo {
918 Bar,
919 }
920 impl Foo {
921 fn baz(self) {
922 match self {
923 Self::Bar<|> => {}
924 }
925 }
926 }
927 ",
928 "Bar ENUM_VARIANT FileId(1) 15..18 15..18",
929 "Bar|Bar",
930 );
931 }
932
933 #[test]
934 fn goto_def_for_enum_variant_self_pattern_record() {
935 check_goto(
936 "
937 //- /lib.rs
938 enum Foo {
939 Bar { val: i32 },
940 }
941 impl Foo {
942 fn baz(self) -> i32 {
943 match self {
944 Self::Bar<|> { val } => {}
945 }
946 }
947 }
948 ",
949 "Bar ENUM_VARIANT FileId(1) 15..31 15..18",
950 "Bar { val: i32 }|Bar",
951 );
952 }
953
954 #[test]
955 fn goto_def_for_enum_variant_self_expr_const() {
956 check_goto(
957 "
958 //- /lib.rs
959 enum Foo {
960 Bar,
961 }
962 impl Foo {
963 fn baz(self) {
964 Self::Bar<|>;
965 }
966 }
967 ",
968 "Bar ENUM_VARIANT FileId(1) 15..18 15..18",
969 "Bar|Bar",
970 );
971 }
972
973 #[test]
974 fn goto_def_for_enum_variant_self_expr_record() {
975 check_goto(
976 "
977 //- /lib.rs
978 enum Foo {
979 Bar { val: i32 },
980 }
981 impl Foo {
982 fn baz(self) {
983 Self::Bar<|> {val: 4};
984 }
985 }
986 ",
987 "Bar ENUM_VARIANT FileId(1) 15..31 15..18",
988 "Bar { val: i32 }|Bar",
989 );
990 }
911} 991}
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index ad78d2d93..76910d09b 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
4use std::sync::Arc; 4use std::sync::Arc;
5 5
6use ra_cfg::CfgOptions; 6use ra_cfg::CfgOptions;
7use ra_db::{CrateName, Env, RelativePathBuf}; 7use ra_db::{CrateName, Env};
8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; 8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
9 9
10use crate::{ 10use crate::{
@@ -28,7 +28,7 @@ impl MockFileData {
28 fn path(&self) -> &str { 28 fn path(&self) -> &str {
29 match self { 29 match self {
30 MockFileData::Plain { path, .. } => path.as_str(), 30 MockFileData::Plain { path, .. } => path.as_str(),
31 MockFileData::Fixture(f) => f.meta.path().as_str(), 31 MockFileData::Fixture(f) => f.meta.path(),
32 } 32 }
33 } 33 }
34 34
@@ -167,7 +167,6 @@ impl MockAnalysis {
167 for (i, data) in self.files.into_iter().enumerate() { 167 for (i, data) in self.files.into_iter().enumerate() {
168 let path = data.path(); 168 let path = data.path();
169 assert!(path.starts_with('/')); 169 assert!(path.starts_with('/'));
170 let path = RelativePathBuf::from_path(&path[1..]).unwrap();
171 let cfg_options = data.cfg_options(); 170 let cfg_options = data.cfg_options();
172 let file_id = FileId(i as u32 + 1); 171 let file_id = FileId(i as u32 + 1);
173 let edition = data.edition(); 172 let edition = data.edition();
@@ -183,7 +182,8 @@ impl MockAnalysis {
183 Default::default(), 182 Default::default(),
184 )); 183 ));
185 } else if path.ends_with("/lib.rs") { 184 } else if path.ends_with("/lib.rs") {
186 let crate_name = path.parent().unwrap().file_name().unwrap(); 185 let base = &path[..path.len() - "/lib.rs".len()];
186 let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
187 let other_crate = crate_graph.add_crate_root( 187 let other_crate = crate_graph.add_crate_root(
188 file_id, 188 file_id,
189 edition, 189 edition,
@@ -199,7 +199,7 @@ impl MockAnalysis {
199 .unwrap(); 199 .unwrap();
200 } 200 }
201 } 201 }
202 change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned())); 202 change.add_file(source_root, file_id, path.into(), Arc::new(data.content().to_owned()));
203 } 203 }
204 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
205 host.apply_change(change); 205 host.apply_change(change);
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 59c230f6c..03f18c617 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -9,6 +9,7 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
9// Search and replace with named wildcards that will match any expression, type, path, pattern or item. 9// Search and replace with named wildcards that will match any expression, type, path, pattern or item.
10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. 10// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. 11// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
12// Within a macro call, a placeholder will match up until whatever token follows the placeholder.
12// Available via the command `rust-analyzer.ssr`. 13// Available via the command `rust-analyzer.ssr`.
13// 14//
14// ```rust 15// ```rust
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index fc716ae82..da26ee669 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -91,14 +91,16 @@ impl<'db> MatchFinder<'db> {
91 if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) { 91 if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) {
92 // Continue searching in each of our placeholders. 92 // Continue searching in each of our placeholders.
93 for placeholder_value in m.placeholder_values.values_mut() { 93 for placeholder_value in m.placeholder_values.values_mut() {
94 // Don't search our placeholder if it's the entire matched node, otherwise we'd 94 if let Some(placeholder_node) = &placeholder_value.node {
95 // find the same match over and over until we got a stack overflow. 95 // Don't search our placeholder if it's the entire matched node, otherwise we'd
96 if placeholder_value.node != *code { 96 // find the same match over and over until we got a stack overflow.
97 self.find_matches( 97 if placeholder_node != code {
98 &placeholder_value.node, 98 self.find_matches(
99 restrict_range, 99 placeholder_node,
100 &mut placeholder_value.inner_matches, 100 restrict_range,
101 ); 101 &mut placeholder_value.inner_matches,
102 );
103 }
102 } 104 }
103 } 105 }
104 matches_out.matches.push(m); 106 matches_out.matches.push(m);
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 265b6d793..bdaba9f1b 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -61,8 +61,9 @@ pub(crate) struct Var(pub String);
61/// Information about a placeholder bound in a match. 61/// Information about a placeholder bound in a match.
62#[derive(Debug)] 62#[derive(Debug)]
63pub(crate) struct PlaceholderMatch { 63pub(crate) struct PlaceholderMatch {
64 /// The node that the placeholder matched to. 64 /// The node that the placeholder matched to. If set, then we'll search for further matches
65 pub(crate) node: SyntaxNode, 65 /// within this node. It isn't set when we match tokens within a macro call's token tree.
66 pub(crate) node: Option<SyntaxNode>,
66 pub(crate) range: FileRange, 67 pub(crate) range: FileRange,
67 /// More matches, found within `node`. 68 /// More matches, found within `node`.
68 pub(crate) inner_matches: SsrMatches, 69 pub(crate) inner_matches: SsrMatches,
@@ -195,6 +196,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
195 SyntaxKind::RECORD_FIELD_LIST => { 196 SyntaxKind::RECORD_FIELD_LIST => {
196 self.attempt_match_record_field_list(match_inputs, pattern, code) 197 self.attempt_match_record_field_list(match_inputs, pattern, code)
197 } 198 }
199 SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code),
198 _ => self.attempt_match_node_children(match_inputs, pattern, code), 200 _ => self.attempt_match_node_children(match_inputs, pattern, code),
199 } 201 }
200 } 202 }
@@ -340,6 +342,90 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
340 Ok(()) 342 Ok(())
341 } 343 }
342 344
345 /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
346 /// tree it can match a sequence of tokens.
347 fn attempt_match_token_tree(
348 &mut self,
349 match_inputs: &MatchInputs,
350 pattern: &SyntaxNode,
351 code: &ra_syntax::SyntaxNode,
352 ) -> Result<(), MatchFailed> {
353 let mut pattern = PatternIterator::new(pattern).peekable();
354 let mut children = code.children_with_tokens();
355 while let Some(child) = children.next() {
356 if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p))
357 {
358 pattern.next();
359 let next_pattern_token = pattern
360 .peek()
361 .and_then(|p| match p {
362 SyntaxElement::Token(t) => Some(t.clone()),
363 SyntaxElement::Node(n) => n.first_token(),
364 })
365 .map(|p| p.text().to_string());
366 let first_matched_token = child.clone();
367 let mut last_matched_token = child;
368 // Read code tokens util we reach one equal to the next token from our pattern
369 // or we reach the end of the token tree.
370 while let Some(next) = children.next() {
371 match &next {
372 SyntaxElement::Token(t) => {
373 if Some(t.to_string()) == next_pattern_token {
374 pattern.next();
375 break;
376 }
377 }
378 SyntaxElement::Node(n) => {
379 if let Some(first_token) = n.first_token() {
380 if Some(first_token.to_string()) == next_pattern_token {
381 if let Some(SyntaxElement::Node(p)) = pattern.next() {
382 // We have a subtree that starts with the next token in our pattern.
383 self.attempt_match_token_tree(match_inputs, &p, &n)?;
384 break;
385 }
386 }
387 }
388 }
389 };
390 last_matched_token = next;
391 }
392 if let Some(match_out) = &mut self.match_out {
393 match_out.placeholder_values.insert(
394 Var(placeholder.ident.to_string()),
395 PlaceholderMatch::from_range(FileRange {
396 file_id: self.sema.original_range(code).file_id,
397 range: first_matched_token
398 .text_range()
399 .cover(last_matched_token.text_range()),
400 }),
401 );
402 }
403 continue;
404 }
405 // Match literal (non-placeholder) tokens.
406 match child {
407 SyntaxElement::Token(token) => {
408 self.attempt_match_token(&mut pattern, &token)?;
409 }
410 SyntaxElement::Node(node) => match pattern.next() {
411 Some(SyntaxElement::Node(p)) => {
412 self.attempt_match_token_tree(match_inputs, &p, &node)?;
413 }
414 Some(SyntaxElement::Token(p)) => fail_match!(
415 "Pattern has token '{}', code has subtree '{}'",
416 p.text(),
417 node.text()
418 ),
419 None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
420 },
421 }
422 }
423 if let Some(p) = pattern.next() {
424 fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
425 }
426 Ok(())
427 }
428
343 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { 429 fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
344 loop { 430 loop {
345 let c = code_it.next(); 431 let c = code_it.next();
@@ -399,7 +485,11 @@ fn recording_match_fail_reasons() -> bool {
399 485
400impl PlaceholderMatch { 486impl PlaceholderMatch {
401 fn new(node: &SyntaxNode, range: FileRange) -> Self { 487 fn new(node: &SyntaxNode, range: FileRange) -> Self {
402 Self { node: node.clone(), range, inner_matches: SsrMatches::default() } 488 Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() }
489 }
490
491 fn from_range(range: FileRange) -> Self {
492 Self { node: None, range, inner_matches: SsrMatches::default() }
403 } 493 }
404} 494}
405 495
@@ -484,7 +574,14 @@ mod tests {
484 assert_eq!(matches.matches.len(), 1); 574 assert_eq!(matches.matches.len(), 1);
485 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); 575 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
486 assert_eq!(matches.matches[0].placeholder_values.len(), 1); 576 assert_eq!(matches.matches[0].placeholder_values.len(), 1);
487 assert_eq!(matches.matches[0].placeholder_values[&Var("x".to_string())].node.text(), "1+2"); 577 assert_eq!(
578 matches.matches[0].placeholder_values[&Var("x".to_string())]
579 .node
580 .as_ref()
581 .unwrap()
582 .text(),
583 "1+2"
584 );
488 585
489 let edit = crate::replacing::matches_to_edit(&matches); 586 let edit = crate::replacing::matches_to_edit(&matches);
490 let mut after = input.to_string(); 587 let mut after = input.to_string();
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs
index 81a5e06a9..5dcde82a2 100644
--- a/crates/ra_ssr/src/replacing.rs
+++ b/crates/ra_ssr/src/replacing.rs
@@ -24,6 +24,7 @@ fn matches_to_edit_at_offset(matches: &SsrMatches, relative_start: TextSize) ->
24 24
25fn render_replace(match_info: &Match) -> String { 25fn render_replace(match_info: &Match) -> String {
26 let mut out = String::new(); 26 let mut out = String::new();
27 let match_start = match_info.matched_node.text_range().start();
27 for r in &match_info.template.tokens { 28 for r in &match_info.template.tokens {
28 match r { 29 match r {
29 PatternElement::Token(t) => out.push_str(t.text.as_str()), 30 PatternElement::Token(t) => out.push_str(t.text.as_str()),
@@ -32,7 +33,14 @@ fn render_replace(match_info: &Match) -> String {
32 match_info.placeholder_values.get(&Var(p.ident.to_string())) 33 match_info.placeholder_values.get(&Var(p.ident.to_string()))
33 { 34 {
34 let range = &placeholder_value.range.range; 35 let range = &placeholder_value.range.range;
35 let mut matched_text = placeholder_value.node.text().to_string(); 36 let mut matched_text = if let Some(node) = &placeholder_value.node {
37 node.text().to_string()
38 } else {
39 let relative_range = range.checked_sub(match_start).unwrap();
40 match_info.matched_node.text().to_string()
41 [usize::from(relative_range.start())..usize::from(relative_range.end())]
42 .to_string()
43 };
36 let edit = 44 let edit =
37 matches_to_edit_at_offset(&placeholder_value.inner_matches, range.start()); 45 matches_to_edit_at_offset(&placeholder_value.inner_matches, range.start());
38 edit.apply(&mut matched_text); 46 edit.apply(&mut matched_text);
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index 4b747fe18..3ee1e74e9 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -427,6 +427,45 @@ fn match_reordered_struct_instantiation() {
427} 427}
428 428
429#[test] 429#[test]
430fn match_macro_invocation() {
431 assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]);
432 assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]);
433 assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}");
434 assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}");
435 assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]);
436}
437
438// When matching within a macro expansion, we only allow matches of nodes that originated from
439// the macro call, not from the macro definition.
440#[test]
441fn no_match_expression_from_macro() {
442 assert_no_match(
443 "$a.clone()",
444 r#"
445 macro_rules! m1 {
446 () => {42.clone()}
447 }
448 fn f1() {m1!()}
449 "#,
450 );
451}
452
453// We definitely don't want to allow matching of an expression that part originates from the
454// macro call `42` and part from the macro definition `.clone()`.
455#[test]
456fn no_match_split_expression() {
457 assert_no_match(
458 "$a.clone()",
459 r#"
460 macro_rules! m1 {
461 ($x:expr) => {$x.clone()}
462 }
463 fn f1() {m1!(42)}
464 "#,
465 );
466}
467
468#[test]
430fn replace_function_call() { 469fn replace_function_call() {
431 assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}"); 470 assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}");
432} 471}
@@ -468,6 +507,20 @@ fn replace_struct_init() {
468} 507}
469 508
470#[test] 509#[test]
510fn replace_macro_invocations() {
511 assert_ssr_transform(
512 "try!($a) ==>> $a?",
513 "fn f1() -> Result<(), E> {bar(try!(foo()));}",
514 "fn f1() -> Result<(), E> {bar(foo()?);}",
515 );
516 assert_ssr_transform(
517 "foo!($a($b)) ==>> foo($b, $a)",
518 "fn f1() {foo!(abc(def() + 2));}",
519 "fn f1() {foo(def() + 2, abc);}",
520 );
521}
522
523#[test]
471fn replace_binary_op() { 524fn replace_binary_op() {
472 assert_ssr_transform( 525 assert_ssr_transform(
473 "$a + $b ==>> $b + $a", 526 "$a + $b ==>> $b + $a",
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 0e2a83c6a..48ce831af 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -29,12 +29,12 @@ fn completes_items_from_standard_library() {
29 let project_start = Instant::now(); 29 let project_start = Instant::now();
30 let server = Project::with_fixture( 30 let server = Project::with_fixture(
31 r#" 31 r#"
32//- Cargo.toml 32//- /Cargo.toml
33[package] 33[package]
34name = "foo" 34name = "foo"
35version = "0.0.0" 35version = "0.0.0"
36 36
37//- src/lib.rs 37//- /src/lib.rs
38use std::collections::Spam; 38use std::collections::Spam;
39"#, 39"#,
40 ) 40 )
@@ -63,24 +63,24 @@ fn test_runnables_project() {
63 } 63 }
64 64
65 let code = r#" 65 let code = r#"
66//- foo/Cargo.toml 66//- /foo/Cargo.toml
67[package] 67[package]
68name = "foo" 68name = "foo"
69version = "0.0.0" 69version = "0.0.0"
70 70
71//- foo/src/lib.rs 71//- /foo/src/lib.rs
72pub fn foo() {} 72pub fn foo() {}
73 73
74//- foo/tests/spam.rs 74//- /foo/tests/spam.rs
75#[test] 75#[test]
76fn test_eggs() {} 76fn test_eggs() {}
77 77
78//- bar/Cargo.toml 78//- /bar/Cargo.toml
79[package] 79[package]
80name = "bar" 80name = "bar"
81version = "0.0.0" 81version = "0.0.0"
82 82
83//- bar/src/main.rs 83//- /bar/src/main.rs
84fn main() {} 84fn main() {}
85"#; 85"#;
86 86
@@ -140,12 +140,12 @@ fn test_format_document() {
140 140
141 let server = project( 141 let server = project(
142 r#" 142 r#"
143//- Cargo.toml 143//- /Cargo.toml
144[package] 144[package]
145name = "foo" 145name = "foo"
146version = "0.0.0" 146version = "0.0.0"
147 147
148//- src/lib.rs 148//- /src/lib.rs
149mod bar; 149mod bar;
150 150
151fn main() { 151fn main() {
@@ -200,13 +200,13 @@ fn test_format_document_2018() {
200 200
201 let server = project( 201 let server = project(
202 r#" 202 r#"
203//- Cargo.toml 203//- /Cargo.toml
204[package] 204[package]
205name = "foo" 205name = "foo"
206version = "0.0.0" 206version = "0.0.0"
207edition = "2018" 207edition = "2018"
208 208
209//- src/lib.rs 209//- /src/lib.rs
210mod bar; 210mod bar;
211 211
212async fn test() { 212async fn test() {
@@ -266,12 +266,12 @@ fn test_missing_module_code_action() {
266 266
267 let server = project( 267 let server = project(
268 r#" 268 r#"
269//- Cargo.toml 269//- /Cargo.toml
270[package] 270[package]
271name = "foo" 271name = "foo"
272version = "0.0.0" 272version = "0.0.0"
273 273
274//- src/lib.rs 274//- /src/lib.rs
275mod bar; 275mod bar;
276 276
277fn main() {} 277fn main() {}
@@ -335,10 +335,10 @@ fn test_missing_module_code_action_in_json_project() {
335 335
336 let code = format!( 336 let code = format!(
337 r#" 337 r#"
338//- rust-project.json 338//- /rust-project.json
339{PROJECT} 339{PROJECT}
340 340
341//- src/lib.rs 341//- /src/lib.rs
342mod bar; 342mod bar;
343 343
344fn main() {{}} 344fn main() {{}}
@@ -391,15 +391,15 @@ fn diagnostics_dont_block_typing() {
391 } 391 }
392 392
393 let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect(); 393 let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
394 let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect(); 394 let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
395 let server = Project::with_fixture(&format!( 395 let server = Project::with_fixture(&format!(
396 r#" 396 r#"
397//- Cargo.toml 397//- /Cargo.toml
398[package] 398[package]
399name = "foo" 399name = "foo"
400version = "0.0.0" 400version = "0.0.0"
401 401
402//- src/lib.rs 402//- /src/lib.rs
403{} 403{}
404 404
405{} 405{}
@@ -449,12 +449,12 @@ fn preserves_dos_line_endings() {
449 449
450 let server = Project::with_fixture( 450 let server = Project::with_fixture(
451 &" 451 &"
452//- Cargo.toml 452//- /Cargo.toml
453[package] 453[package]
454name = \"foo\" 454name = \"foo\"
455version = \"0.0.0\" 455version = \"0.0.0\"
456 456
457//- src/main.rs 457//- /src/main.rs
458/// Some Docs\r\nfn main() {} 458/// Some Docs\r\nfn main() {}
459", 459",
460 ) 460 )
@@ -484,12 +484,12 @@ fn out_dirs_check() {
484 484
485 let server = Project::with_fixture( 485 let server = Project::with_fixture(
486 r###" 486 r###"
487//- Cargo.toml 487//- /Cargo.toml
488[package] 488[package]
489name = "foo" 489name = "foo"
490version = "0.0.0" 490version = "0.0.0"
491 491
492//- build.rs 492//- /build.rs
493use std::{env, fs, path::Path}; 493use std::{env, fs, path::Path};
494 494
495fn main() { 495fn main() {
@@ -504,7 +504,7 @@ fn main() {
504 println!("cargo:rustc-cfg=featlike=\"set\""); 504 println!("cargo:rustc-cfg=featlike=\"set\"");
505 println!("cargo:rerun-if-changed=build.rs"); 505 println!("cargo:rerun-if-changed=build.rs");
506} 506}
507//- src/main.rs 507//- /src/main.rs
508#[rustc_builtin_macro] macro_rules! include {} 508#[rustc_builtin_macro] macro_rules! include {}
509#[rustc_builtin_macro] macro_rules! concat {} 509#[rustc_builtin_macro] macro_rules! concat {}
510#[rustc_builtin_macro] macro_rules! env {} 510#[rustc_builtin_macro] macro_rules! env {}
@@ -599,7 +599,7 @@ fn resolve_proc_macro() {
599 } 599 }
600 let server = Project::with_fixture( 600 let server = Project::with_fixture(
601 r###" 601 r###"
602//- foo/Cargo.toml 602//- /foo/Cargo.toml
603[package] 603[package]
604name = "foo" 604name = "foo"
605version = "0.0.0" 605version = "0.0.0"
@@ -607,7 +607,7 @@ edition = "2018"
607[dependencies] 607[dependencies]
608bar = {path = "../bar"} 608bar = {path = "../bar"}
609 609
610//- foo/src/main.rs 610//- /foo/src/main.rs
611use bar::Bar; 611use bar::Bar;
612trait Bar { 612trait Bar {
613 fn bar(); 613 fn bar();
@@ -618,7 +618,7 @@ fn main() {
618 Foo::bar(); 618 Foo::bar();
619} 619}
620 620
621//- bar/Cargo.toml 621//- /bar/Cargo.toml
622[package] 622[package]
623name = "bar" 623name = "bar"
624version = "0.0.0" 624version = "0.0.0"
@@ -627,7 +627,7 @@ edition = "2018"
627[lib] 627[lib]
628proc-macro = true 628proc-macro = true
629 629
630//- bar/src/lib.rs 630//- /bar/src/lib.rs
631extern crate proc_macro; 631extern crate proc_macro;
632use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; 632use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
633macro_rules! t { 633macro_rules! t {
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 30d03b622..f58790ded 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -69,7 +69,7 @@ impl<'a> Project<'a> {
69 let mut paths = vec![]; 69 let mut paths = vec![];
70 70
71 for entry in parse_fixture(self.fixture) { 71 for entry in parse_fixture(self.fixture) {
72 let path = tmp_dir.path().join(entry.meta.path().as_str()); 72 let path = tmp_dir.path().join(&entry.meta.path()['/'.len_utf8()..]);
73 fs::create_dir_all(path.parent().unwrap()).unwrap(); 73 fs::create_dir_all(path.parent().unwrap()).unwrap();
74 fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); 74 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
75 paths.push((path, entry.text)); 75 paths.push((path, entry.text));
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 981565cd7..b1333cf15 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -168,13 +168,13 @@ pub struct FixtureEntry {
168 168
169#[derive(Debug, Eq, PartialEq)] 169#[derive(Debug, Eq, PartialEq)]
170pub enum FixtureMeta { 170pub enum FixtureMeta {
171 Root { path: RelativePathBuf }, 171 Root { path: String },
172 File(FileMeta), 172 File(FileMeta),
173} 173}
174 174
175#[derive(Debug, Eq, PartialEq)] 175#[derive(Debug, Eq, PartialEq)]
176pub struct FileMeta { 176pub struct FileMeta {
177 pub path: RelativePathBuf, 177 pub path: String,
178 pub crate_name: Option<String>, 178 pub crate_name: Option<String>,
179 pub deps: Vec<String>, 179 pub deps: Vec<String>,
180 pub cfg: CfgOptions, 180 pub cfg: CfgOptions,
@@ -183,7 +183,7 @@ pub struct FileMeta {
183} 183}
184 184
185impl FixtureMeta { 185impl FixtureMeta {
186 pub fn path(&self) -> &RelativePath { 186 pub fn path(&self) -> &str {
187 match self { 187 match self {
188 FixtureMeta::Root { path } => &path, 188 FixtureMeta::Root { path } => &path,
189 FixtureMeta::File(f) => &f.path, 189 FixtureMeta::File(f) => &f.path,
@@ -292,12 +292,12 @@ fn parse_meta(meta: &str) -> FixtureMeta {
292 let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); 292 let components = meta.split_ascii_whitespace().collect::<Vec<_>>();
293 293
294 if components[0] == "root" { 294 if components[0] == "root" {
295 let path: RelativePathBuf = components[1].into(); 295 let path = components[1].to_string();
296 assert!(path.starts_with("/") && path.ends_with("/")); 296 assert!(path.starts_with("/") && path.ends_with("/"));
297 return FixtureMeta::Root { path }; 297 return FixtureMeta::Root { path };
298 } 298 }
299 299
300 let path: RelativePathBuf = components[0].into(); 300 let path = components[0].to_string();
301 assert!(path.starts_with("/")); 301 assert!(path.starts_with("/"));
302 302
303 let mut krate = None; 303 let mut krate = None;