aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/merge_imports.rs161
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs144
-rw-r--r--crates/assists/src/utils/insert_use.rs35
-rw-r--r--crates/base_db/src/input.rs8
-rw-r--r--crates/hir/src/semantics.rs8
-rw-r--r--crates/hir/src/source_analyzer.rs4
-rw-r--r--crates/hir_def/src/body/lower.rs6
-rw-r--r--crates/hir_def/src/expr.rs2
-rw-r--r--crates/hir_ty/src/infer.rs6
-rw-r--r--crates/hir_ty/src/infer/pat.rs16
-rw-r--r--crates/hir_ty/src/tests/patterns.rs25
-rw-r--r--crates/hir_ty/src/traits/chalk.rs14
-rw-r--r--crates/hir_ty/src/traits/chalk/interner.rs18
-rw-r--r--crates/hir_ty/src/traits/chalk/mapping.rs12
-rw-r--r--crates/ide/src/completion.rs2
-rw-r--r--crates/ide/src/completion/complete_attribute.rs4
-rw-r--r--crates/ide/src/completion/complete_mod.rs324
-rw-r--r--crates/ide/src/completion/complete_qualified_path.rs2
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs1
-rw-r--r--crates/ide/src/completion/completion_context.rs7
-rw-r--r--crates/ide/src/completion/patterns.rs1
-rw-r--r--crates/ide/src/syntax_highlighting.rs63
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html13
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs13
-rw-r--r--crates/ide_db/src/defs.rs10
-rw-r--r--crates/parser/src/grammar/patterns.rs4
-rw-r--r--crates/project_model/src/lib.rs32
-rw-r--r--crates/project_model/src/project_json.rs6
-rw-r--r--crates/project_model/src/sysroot.rs13
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt45
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs51
-rw-r--r--crates/rust-analyzer/src/reload.rs2
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rast (renamed from crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rast)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rs (renamed from crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rs)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast (renamed from crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rast)0
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs (renamed from crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rs)0
-rw-r--r--crates/vfs/src/file_set.rs13
-rw-r--r--crates/vfs/src/vfs_path.rs74
38 files changed, 936 insertions, 203 deletions
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index 35b884206..0bd679260 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -1,14 +1,14 @@
1use std::iter::successors;
2
3use syntax::{ 1use syntax::{
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter}, 2 algo::{neighbor, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make}, 3 ast, AstNode,
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 4};
8 5
9use crate::{ 6use crate::{
10 assist_context::{AssistContext, Assists}, 7 assist_context::{AssistContext, Assists},
11 utils::next_prev, 8 utils::{
9 insert_use::{try_merge_imports, try_merge_trees},
10 next_prev, MergeBehaviour,
11 },
12 AssistId, AssistKind, 12 AssistId, AssistKind,
13}; 13};
14 14
@@ -30,23 +30,22 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
30 let mut offset = ctx.offset(); 30 let mut offset = ctx.offset();
31 31
32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
33 let (merged, to_delete) = next_prev() 33 let (merged, to_delete) =
34 .filter_map(|dir| neighbor(&use_item, dir)) 34 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
35 .filter_map(|it| Some((it.clone(), it.use_tree()?))) 35 try_merge_imports(&use_item, &use_item2, MergeBehaviour::Full).zip(Some(use_item2))
36 .find_map(|(use_item, use_tree)| {
37 Some((try_merge_trees(&tree, &use_tree)?, use_item))
38 })?; 36 })?;
39 37
40 rewriter.replace_ast(&tree, &merged); 38 rewriter.replace_ast(&use_item, &merged);
41 rewriter += to_delete.remove(); 39 rewriter += to_delete.remove();
42 40
43 if to_delete.syntax().text_range().end() < offset { 41 if to_delete.syntax().text_range().end() < offset {
44 offset -= to_delete.syntax().text_range().len(); 42 offset -= to_delete.syntax().text_range().len();
45 } 43 }
46 } else { 44 } else {
47 let (merged, to_delete) = next_prev() 45 let (merged, to_delete) =
48 .filter_map(|dir| neighbor(&tree, dir)) 46 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
49 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; 47 try_merge_trees(&tree, &use_tree, MergeBehaviour::Full).zip(Some(use_tree))
48 })?;
50 49
51 rewriter.replace_ast(&tree, &merged); 50 rewriter.replace_ast(&tree, &merged);
52 rewriter += to_delete.remove(); 51 rewriter += to_delete.remove();
@@ -67,66 +66,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
67 ) 66 )
68} 67}
69 68
70fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
71 let lhs_path = old.path()?;
72 let rhs_path = new.path()?;
73
74 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
75
76 let lhs = old.split_prefix(&lhs_prefix);
77 let rhs = new.split_prefix(&rhs_prefix);
78
79 let should_insert_comma = lhs
80 .use_tree_list()?
81 .r_curly_token()
82 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
83 .map(|it| it.kind() != T![,])
84 .unwrap_or(true);
85
86 let mut to_insert: Vec<SyntaxElement> = Vec::new();
87 if should_insert_comma {
88 to_insert.push(make::token(T![,]).into());
89 to_insert.push(make::tokens::single_space().into());
90 }
91 to_insert.extend(
92 rhs.use_tree_list()?
93 .syntax()
94 .children_with_tokens()
95 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
96 );
97 let use_tree_list = lhs.use_tree_list()?;
98 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
99 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
100 Some(lhs.with_use_tree_list(use_tree_list))
101}
102
103fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
104 let mut res = None;
105 let mut lhs_curr = first_path(&lhs);
106 let mut rhs_curr = first_path(&rhs);
107 loop {
108 match (lhs_curr.segment(), rhs_curr.segment()) {
109 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
110 _ => break,
111 }
112 res = Some((lhs_curr.clone(), rhs_curr.clone()));
113
114 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
115 (Some(lhs), Some(rhs)) => {
116 lhs_curr = lhs;
117 rhs_curr = rhs;
118 }
119 _ => break,
120 }
121 }
122
123 res
124}
125
126fn first_path(path: &ast::Path) -> ast::Path {
127 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
128}
129
130#[cfg(test)] 69#[cfg(test)]
131mod tests { 70mod tests {
132 use crate::tests::{check_assist, check_assist_not_applicable}; 71 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -189,6 +128,78 @@ use std::{fmt::{Display, self}};
189 } 128 }
190 129
191 #[test] 130 #[test]
131 fn skip_pub1() {
132 check_assist_not_applicable(
133 merge_imports,
134 r"
135pub use std::fmt<|>::Debug;
136use std::fmt::Display;
137",
138 );
139 }
140
141 #[test]
142 fn skip_pub_last() {
143 check_assist_not_applicable(
144 merge_imports,
145 r"
146use std::fmt<|>::Debug;
147pub use std::fmt::Display;
148",
149 );
150 }
151
152 #[test]
153 fn skip_pub_crate_pub() {
154 check_assist_not_applicable(
155 merge_imports,
156 r"
157pub(crate) use std::fmt<|>::Debug;
158pub use std::fmt::Display;
159",
160 );
161 }
162
163 #[test]
164 fn skip_pub_pub_crate() {
165 check_assist_not_applicable(
166 merge_imports,
167 r"
168pub use std::fmt<|>::Debug;
169pub(crate) use std::fmt::Display;
170",
171 );
172 }
173
174 #[test]
175 fn merge_pub() {
176 check_assist(
177 merge_imports,
178 r"
179pub use std::fmt<|>::Debug;
180pub use std::fmt::Display;
181",
182 r"
183pub use std::fmt::{Debug, Display};
184",
185 )
186 }
187
188 #[test]
189 fn merge_pub_crate() {
190 check_assist(
191 merge_imports,
192 r"
193pub(crate) use std::fmt<|>::Debug;
194pub(crate) use std::fmt::Display;
195",
196 r"
197pub(crate) use std::fmt::{Debug, Display};
198",
199 )
200 }
201
202 #[test]
192 fn test_merge_nested() { 203 fn test_merge_nested() {
193 check_assist( 204 check_assist(
194 merge_imports, 205 merge_imports,
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 4e252edf0..a8ab2aecc 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 TextRange, TextSize, T, 3 SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -22,62 +22,108 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// ``` 22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?;
25 26
26 if !is_valid_macrocall(&macro_call, "dbg")? { 27 let macro_text_range = macro_call.syntax().text_range();
27 return None;
28 }
29
30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31
32 let macro_end = if macro_call.semicolon_token().is_some() { 28 let macro_end = if macro_call.semicolon_token().is_some() {
33 macro_call.syntax().text_range().end() - TextSize::of(';') 29 macro_text_range.end() - TextSize::of(';')
34 } else { 30 } else {
35 macro_call.syntax().text_range().end() 31 macro_text_range.end()
36 }; 32 };
37 33
38 // macro_range determines what will be deleted and replaced with macro_content 34 acc.add(
39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); 35 AssistId("remove_dbg", AssistKind::Refactor),
40 let paste_instead_of_dbg = { 36 "Remove dbg!()",
41 let text = macro_call.token_tree()?.syntax().text(); 37 macro_text_range,
42 38 |builder| {
43 // leafiness determines if we should include the parenthesis or not 39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
44 let slice_index: TextRange = if is_leaf { 40 },
45 // leaf means - we can extract the contents of the dbg! in text 41 )
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) 42}
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
52 };
53 43
54 let target = macro_call.syntax().text_range(); 44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { 45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
56 builder.replace(macro_range, paste_instead_of_dbg); 46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 let is_leaf = macro_call.syntax().next_sibling().is_none();
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) {
54 format!("({})", macro_text_in_brackets)
55 } else {
56 macro_text_in_brackets.to_string()
57 }) 57 })
58} 58}
59 59
60/// Verifies that the given macro_call actually matches the given name 60/// Verifies that the given macro_call actually matches the given name
61/// and contains proper ending tokens 61/// and contains proper ending tokens, then returns the contents between the ending tokens
62fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { 62fn get_valid_macrocall_contents(
63 macro_call: &ast::MacroCall,
64 macro_name: &str,
65) -> Option<Vec<SyntaxElement>> {
63 let path = macro_call.path()?; 66 let path = macro_call.path()?;
64 let name_ref = path.segment()?.name_ref()?; 67 let name_ref = path.segment()?.name_ref()?;
65 68
66 // Make sure it is actually a dbg-macro call, dbg followed by ! 69 // Make sure it is actually a dbg-macro call, dbg followed by !
67 let excl = path.syntax().next_sibling_or_token()?; 70 let excl = path.syntax().next_sibling_or_token()?;
68
69 if name_ref.text() != macro_name || excl.kind() != T![!] { 71 if name_ref.text() != macro_name || excl.kind() != T![!] {
70 return None; 72 return None;
71 } 73 }
72 74
73 let node = macro_call.token_tree()?.syntax().clone(); 75 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
74 let first_child = node.first_child_or_token()?; 76 let first_child = children_with_tokens.next()?;
75 let last_child = node.last_child_or_token()?; 77 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
78 let last_child = contents_between_brackets.pop()?;
79
80 if contents_between_brackets.is_empty() {
81 None
82 } else {
83 match (first_child.kind(), last_child.kind()) {
84 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
85 Some(contents_between_brackets)
86 }
87 _ => None,
88 }
89 }
90}
76 91
77 match (first_child.kind(), last_child.kind()) { 92fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
78 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), 93 if macro_contents.len() < 2 {
79 _ => Some(false), 94 return false;
80 } 95 }
96 let mut unpaired_brackets_in_contents = Vec::new();
97 for element in macro_contents {
98 match element.kind() {
99 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
100 T![')'] => {
101 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
102 {
103 return true;
104 }
105 }
106 T![']'] => {
107 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
108 {
109 return true;
110 }
111 }
112 T!['}'] => {
113 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
114 {
115 return true;
116 }
117 }
118 symbol_kind => {
119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
120 if symbol_not_in_bracket && symbol_kind.is_punct() {
121 return true;
122 }
123 }
124 }
125 }
126 !unpaired_brackets_in_contents.is_empty()
81} 127}
82 128
83#[cfg(test)] 129#[cfg(test)]
@@ -157,12 +203,38 @@ fn foo(n: usize) {
157 } 203 }
158 204
159 #[test] 205 #[test]
206 fn remove_dbg_from_non_leaf_simple_expression() {
207 check_assist(
208 remove_dbg,
209 "
210fn main() {
211 let mut a = 1;
212 while dbg!<|>(a) < 10000 {
213 a += 1;
214 }
215}
216",
217 "
218fn main() {
219 let mut a = 1;
220 while a < 10000 {
221 a += 1;
222 }
223}
224",
225 );
226 }
227
228 #[test]
160 fn test_remove_dbg_keep_expression() { 229 fn test_remove_dbg_keep_expression() {
161 check_assist( 230 check_assist(
162 remove_dbg, 231 remove_dbg,
163 r#"let res = <|>dbg!(a + b).foo();"#, 232 r#"let res = <|>dbg!(a + b).foo();"#,
164 r#"let res = (a + b).foo();"#, 233 r#"let res = (a + b).foo();"#,
165 ); 234 );
235
236 check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
237 check_assist(remove_dbg, r#"let res = <|>dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
166 } 238 }
167 239
168 #[test] 240 #[test]
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 8a4c8520d..98553b2e0 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -138,13 +138,23 @@ pub(crate) fn insert_use(
138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert) 138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
139} 139}
140 140
141fn try_merge_imports( 141fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
142 match (vis0, vis1) {
143 (None, None) => true,
144 // FIXME: Don't use the string representation to check for equality
145 // spaces inside of the node would break this comparison
146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
147 _ => false,
148 }
149}
150
151pub(crate) fn try_merge_imports(
142 old: &ast::Use, 152 old: &ast::Use,
143 new: &ast::Use, 153 new: &ast::Use,
144 merge_behaviour: MergeBehaviour, 154 merge_behaviour: MergeBehaviour,
145) -> Option<ast::Use> { 155) -> Option<ast::Use> {
146 // don't merge into re-exports 156 // don't merge imports with different visibilities
147 if old.visibility().and_then(|vis| vis.pub_token()).is_some() { 157 if !eq_visibility(old.visibility(), new.visibility()) {
148 return None; 158 return None;
149 } 159 }
150 let old_tree = old.use_tree()?; 160 let old_tree = old.use_tree()?;
@@ -161,7 +171,7 @@ fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool {
161} 171}
162 172
163// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion 173// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion
164pub fn try_merge_trees( 174pub(crate) fn try_merge_trees(
165 old: &ast::UseTree, 175 old: &ast::UseTree,
166 new: &ast::UseTree, 176 new: &ast::UseTree,
167 merge_behaviour: MergeBehaviour, 177 merge_behaviour: MergeBehaviour,
@@ -278,7 +288,8 @@ fn first_path(path: &ast::Path) -> ast::Path {
278} 288}
279 289
280fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { 290fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
281 path.syntax().children().flat_map(ast::PathSegment::cast) 291 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
292 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
282} 293}
283 294
284#[derive(PartialEq, Eq)] 295#[derive(PartialEq, Eq)]
@@ -684,8 +695,18 @@ use std::io;",
684 check_last( 695 check_last(
685 "foo::bar", 696 "foo::bar",
686 r"use foo::bar::baz::Qux;", 697 r"use foo::bar::baz::Qux;",
687 r"use foo::bar::baz::Qux; 698 r"use foo::bar;
688use foo::bar;", 699use foo::bar::baz::Qux;",
700 );
701 }
702
703 #[test]
704 fn insert_short_before_long() {
705 check_none(
706 "foo::bar",
707 r"use foo::bar::baz::Qux;",
708 r"use foo::bar;
709use foo::bar::baz::Qux;",
689 ); 710 );
690 } 711 }
691 712
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index f3d65cdf0..9a61f1d56 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -12,7 +12,7 @@ use cfg::CfgOptions;
12use rustc_hash::{FxHashMap, FxHashSet}; 12use rustc_hash::{FxHashMap, FxHashSet};
13use syntax::SmolStr; 13use syntax::SmolStr;
14use tt::TokenExpander; 14use tt::TokenExpander;
15use vfs::file_set::FileSet; 15use vfs::{file_set::FileSet, VfsPath};
16 16
17pub use vfs::FileId; 17pub use vfs::FileId;
18 18
@@ -43,6 +43,12 @@ impl SourceRoot {
43 pub fn new_library(file_set: FileSet) -> SourceRoot { 43 pub fn new_library(file_set: FileSet) -> SourceRoot {
44 SourceRoot { is_library: true, file_set } 44 SourceRoot { is_library: true, file_set }
45 } 45 }
46 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
47 self.file_set.path_for_file(file)
48 }
49 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
50 self.file_set.file_for_path(path)
51 }
46 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { 52 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
47 self.file_set.iter() 53 self.file_set.iter()
48 } 54 }
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 1594d4f0f..0516a05b4 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -207,8 +207,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
207 self.imp.resolve_record_field(field) 207 self.imp.resolve_record_field(field)
208 } 208 }
209 209
210 pub fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> { 210 pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
211 self.imp.resolve_record_field_pat(field) 211 self.imp.resolve_record_pat_field(field)
212 } 212 }
213 213
214 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { 214 pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
@@ -433,8 +433,8 @@ impl<'db> SemanticsImpl<'db> {
433 self.analyze(field.syntax()).resolve_record_field(self.db, field) 433 self.analyze(field.syntax()).resolve_record_field(self.db, field)
434 } 434 }
435 435
436 fn resolve_record_field_pat(&self, field: &ast::RecordPatField) -> Option<Field> { 436 fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
437 self.analyze(field.syntax()).resolve_record_field_pat(self.db, field) 437 self.analyze(field.syntax()).resolve_record_pat_field(self.db, field)
438 } 438 }
439 439
440 fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { 440 fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 1d13c4f1d..1aef0f33f 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -179,13 +179,13 @@ impl SourceAnalyzer {
179 Some((struct_field.into(), local)) 179 Some((struct_field.into(), local))
180 } 180 }
181 181
182 pub(crate) fn resolve_record_field_pat( 182 pub(crate) fn resolve_record_pat_field(
183 &self, 183 &self,
184 _db: &dyn HirDatabase, 184 _db: &dyn HirDatabase,
185 field: &ast::RecordPatField, 185 field: &ast::RecordPatField,
186 ) -> Option<Field> { 186 ) -> Option<Field> {
187 let pat_id = self.pat_id(&field.pat()?)?; 187 let pat_id = self.pat_id(&field.pat()?)?;
188 let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?; 188 let struct_field = self.infer.as_ref()?.record_pat_field_resolution(pat_id)?;
189 Some(struct_field.into()) 189 Some(struct_field.into())
190 } 190 }
191 191
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index c5ebc2aa0..2d91bb21f 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -838,8 +838,12 @@ impl ExprCollector<'_> {
838 838
839 Pat::Missing 839 Pat::Missing
840 } 840 }
841 ast::Pat::BoxPat(boxpat) => {
842 let inner = self.collect_pat_opt(boxpat.pat());
843 Pat::Box { inner }
844 }
841 // FIXME: implement 845 // FIXME: implement
842 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, 846 ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
843 }; 847 };
844 let ptr = AstPtr::new(&pat); 848 let ptr = AstPtr::new(&pat);
845 self.alloc_pat(pattern, Either::Left(ptr)) 849 self.alloc_pat(pattern, Either::Left(ptr))
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index e862c2080..e5d740a36 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -398,6 +398,7 @@ pub enum Pat {
398 Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> }, 398 Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
399 TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> }, 399 TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
400 Ref { pat: PatId, mutability: Mutability }, 400 Ref { pat: PatId, mutability: Mutability },
401 Box { inner: PatId },
401} 402}
402 403
403impl Pat { 404impl Pat {
@@ -418,6 +419,7 @@ impl Pat {
418 Pat::Record { args, .. } => { 419 Pat::Record { args, .. } => {
419 args.iter().map(|f| f.pat).for_each(f); 420 args.iter().map(|f| f.pat).for_each(f);
420 } 421 }
422 Pat::Box { inner } => f(*inner),
421 } 423 }
422 } 424 }
423} 425}
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 03b00b101..2b53b8297 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -125,7 +125,7 @@ pub struct InferenceResult {
125 field_resolutions: FxHashMap<ExprId, FieldId>, 125 field_resolutions: FxHashMap<ExprId, FieldId>,
126 /// For each field in record literal, records the field it resolves to. 126 /// For each field in record literal, records the field it resolves to.
127 record_field_resolutions: FxHashMap<ExprId, FieldId>, 127 record_field_resolutions: FxHashMap<ExprId, FieldId>,
128 record_field_pat_resolutions: FxHashMap<PatId, FieldId>, 128 record_pat_field_resolutions: FxHashMap<PatId, FieldId>,
129 /// For each struct literal, records the variant it resolves to. 129 /// For each struct literal, records the variant it resolves to.
130 variant_resolutions: FxHashMap<ExprOrPatId, VariantId>, 130 variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
131 /// For each associated item record what it resolves to 131 /// For each associated item record what it resolves to
@@ -146,8 +146,8 @@ impl InferenceResult {
146 pub fn record_field_resolution(&self, expr: ExprId) -> Option<FieldId> { 146 pub fn record_field_resolution(&self, expr: ExprId) -> Option<FieldId> {
147 self.record_field_resolutions.get(&expr).copied() 147 self.record_field_resolutions.get(&expr).copied()
148 } 148 }
149 pub fn record_field_pat_resolution(&self, pat: PatId) -> Option<FieldId> { 149 pub fn record_pat_field_resolution(&self, pat: PatId) -> Option<FieldId> {
150 self.record_field_pat_resolutions.get(&pat).copied() 150 self.record_pat_field_resolutions.get(&pat).copied()
151 } 151 }
152 pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> { 152 pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> {
153 self.variant_resolutions.get(&id.into()).copied() 153 self.variant_resolutions.get(&id.into()).copied()
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index 4dd4f9802..cde2ab82b 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -70,7 +70,7 @@ impl<'a> InferenceContext<'a> {
70 let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); 70 let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name));
71 if let Some(local_id) = matching_field { 71 if let Some(local_id) = matching_field {
72 let field_def = FieldId { parent: def.unwrap(), local_id }; 72 let field_def = FieldId { parent: def.unwrap(), local_id };
73 self.result.record_field_pat_resolutions.insert(subpat.pat, field_def); 73 self.result.record_pat_field_resolutions.insert(subpat.pat, field_def);
74 } 74 }
75 75
76 let expected_ty = 76 let expected_ty =
@@ -209,6 +209,18 @@ impl<'a> InferenceContext<'a> {
209 end_ty 209 end_ty
210 } 210 }
211 Pat::Lit(expr) => self.infer_expr(*expr, &Expectation::has_type(expected.clone())), 211 Pat::Lit(expr) => self.infer_expr(*expr, &Expectation::has_type(expected.clone())),
212 Pat::Box { inner } => match self.resolve_boxed_box() {
213 Some(box_adt) => {
214 let inner_expected = match expected.as_adt() {
215 Some((adt, substs)) if adt == box_adt => substs.as_single(),
216 _ => &Ty::Unknown,
217 };
218
219 let inner_ty = self.infer_pat(*inner, inner_expected, default_bm);
220 Ty::apply_one(TypeCtor::Adt(box_adt), inner_ty)
221 }
222 None => Ty::Unknown,
223 },
212 Pat::Missing => Ty::Unknown, 224 Pat::Missing => Ty::Unknown,
213 }; 225 };
214 // use a new type variable if we got Ty::Unknown here 226 // use a new type variable if we got Ty::Unknown here
@@ -236,6 +248,6 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
236 Expr::Literal(Literal::String(..)) => false, 248 Expr::Literal(Literal::String(..)) => false,
237 _ => true, 249 _ => true,
238 }, 250 },
239 Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Missing => false, 251 Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
240 } 252 }
241} 253}
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index aeb191c79..6a965ac4f 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -654,3 +654,28 @@ fn slice_tail_pattern() {
654 "#]], 654 "#]],
655 ); 655 );
656} 656}
657
658#[test]
659fn box_pattern() {
660 check_infer(
661 r#"
662 #[lang = "owned_box"]
663 pub struct Box<T>(T);
664
665 fn foo(params: Box<i32>) {
666 match params {
667 box integer => {}
668 }
669 }
670 "#,
671 expect![[r#"
672 52..58 'params': Box<i32>
673 70..124 '{ ... } }': ()
674 76..122 'match ... }': ()
675 82..88 'params': Box<i32>
676 99..110 'box integer': Box<i32>
677 103..110 'integer': i32
678 114..116 '{}': ()
679 "#]],
680 );
681}
diff --git a/crates/hir_ty/src/traits/chalk.rs b/crates/hir_ty/src/traits/chalk.rs
index ff364789e..57d0a32df 100644
--- a/crates/hir_ty/src/traits/chalk.rs
+++ b/crates/hir_ty/src/traits/chalk.rs
@@ -307,13 +307,17 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
307 let id = from_chalk(self.db, trait_id); 307 let id = from_chalk(self.db, trait_id);
308 self.db.trait_data(id).name.to_string() 308 self.db.trait_data(id).name.to_string()
309 } 309 }
310 // FIXME: lookup names 310 fn adt_name(&self, adt_id: chalk_ir::AdtId<Interner>) -> String {
311 fn adt_name(&self, struct_id: chalk_ir::AdtId<Interner>) -> String { 311 let id = from_chalk(self.db, adt_id);
312 let datum = self.db.struct_datum(self.krate, struct_id); 312 match id {
313 format!("{:?}", datum.name(&Interner)) 313 hir_def::AdtId::StructId(id) => self.db.struct_data(id).name.to_string(),
314 hir_def::AdtId::EnumId(id) => self.db.enum_data(id).name.to_string(),
315 hir_def::AdtId::UnionId(id) => self.db.union_data(id).name.to_string(),
316 }
314 } 317 }
315 fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId<Interner>) -> String { 318 fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId<Interner>) -> String {
316 format!("Assoc_{}", assoc_ty_id.0) 319 let id = self.db.associated_ty_data(assoc_ty_id).name;
320 self.db.type_alias_data(id).name.to_string()
317 } 321 }
318 fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId<Interner>) -> String { 322 fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId<Interner>) -> String {
319 format!("Opaque_{}", opaque_ty_id.0) 323 format!("Opaque_{}", opaque_ty_id.0)
diff --git a/crates/hir_ty/src/traits/chalk/interner.rs b/crates/hir_ty/src/traits/chalk/interner.rs
index fc0f9c201..eb35db3ff 100644
--- a/crates/hir_ty/src/traits/chalk/interner.rs
+++ b/crates/hir_ty/src/traits/chalk/interner.rs
@@ -26,7 +26,7 @@ pub type OpaqueTyId = chalk_ir::OpaqueTyId<Interner>;
26pub type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>; 26pub type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>;
27 27
28impl chalk_ir::interner::Interner for Interner { 28impl chalk_ir::interner::Interner for Interner {
29 type InternedType = Box<chalk_ir::TyData<Self>>; // FIXME use Arc? 29 type InternedType = Arc<chalk_ir::TyData<Self>>;
30 type InternedLifetime = chalk_ir::LifetimeData<Self>; 30 type InternedLifetime = chalk_ir::LifetimeData<Self>;
31 type InternedConst = Arc<chalk_ir::ConstData<Self>>; 31 type InternedConst = Arc<chalk_ir::ConstData<Self>>;
32 type InternedConcreteConst = (); 32 type InternedConcreteConst = ();
@@ -34,7 +34,7 @@ impl chalk_ir::interner::Interner for Interner {
34 type InternedGoal = Arc<GoalData<Self>>; 34 type InternedGoal = Arc<GoalData<Self>>;
35 type InternedGoals = Vec<Goal<Self>>; 35 type InternedGoals = Vec<Goal<Self>>;
36 type InternedSubstitution = Vec<GenericArg<Self>>; 36 type InternedSubstitution = Vec<GenericArg<Self>>;
37 type InternedProgramClause = chalk_ir::ProgramClauseData<Self>; 37 type InternedProgramClause = Arc<chalk_ir::ProgramClauseData<Self>>;
38 type InternedProgramClauses = Arc<[chalk_ir::ProgramClause<Self>]>; 38 type InternedProgramClauses = Arc<[chalk_ir::ProgramClause<Self>]>;
39 type InternedQuantifiedWhereClauses = Vec<chalk_ir::QuantifiedWhereClause<Self>>; 39 type InternedQuantifiedWhereClauses = Vec<chalk_ir::QuantifiedWhereClause<Self>>;
40 type InternedVariableKinds = Vec<chalk_ir::VariableKind<Self>>; 40 type InternedVariableKinds = Vec<chalk_ir::VariableKind<Self>>;
@@ -197,11 +197,11 @@ impl chalk_ir::interner::Interner for Interner {
197 tls::with_current_program(|prog| Some(prog?.debug_quantified_where_clauses(clauses, fmt))) 197 tls::with_current_program(|prog| Some(prog?.debug_quantified_where_clauses(clauses, fmt)))
198 } 198 }
199 199
200 fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Box<chalk_ir::TyData<Self>> { 200 fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Arc<chalk_ir::TyData<Self>> {
201 Box::new(ty) 201 Arc::new(ty)
202 } 202 }
203 203
204 fn ty_data<'a>(&self, ty: &'a Box<chalk_ir::TyData<Self>>) -> &'a chalk_ir::TyData<Self> { 204 fn ty_data<'a>(&self, ty: &'a Arc<chalk_ir::TyData<Self>>) -> &'a chalk_ir::TyData<Self> {
205 ty 205 ty
206 } 206 }
207 207
@@ -230,7 +230,7 @@ impl chalk_ir::interner::Interner for Interner {
230 constant 230 constant
231 } 231 }
232 232
233 fn const_eq(&self, _ty: &Box<chalk_ir::TyData<Self>>, _c1: &(), _c2: &()) -> bool { 233 fn const_eq(&self, _ty: &Arc<chalk_ir::TyData<Self>>, _c1: &(), _c2: &()) -> bool {
234 true 234 true
235 } 235 }
236 236
@@ -284,13 +284,13 @@ impl chalk_ir::interner::Interner for Interner {
284 fn intern_program_clause( 284 fn intern_program_clause(
285 &self, 285 &self,
286 data: chalk_ir::ProgramClauseData<Self>, 286 data: chalk_ir::ProgramClauseData<Self>,
287 ) -> chalk_ir::ProgramClauseData<Self> { 287 ) -> Arc<chalk_ir::ProgramClauseData<Self>> {
288 data 288 Arc::new(data)
289 } 289 }
290 290
291 fn program_clause_data<'a>( 291 fn program_clause_data<'a>(
292 &self, 292 &self,
293 clause: &'a chalk_ir::ProgramClauseData<Self>, 293 clause: &'a Arc<chalk_ir::ProgramClauseData<Self>>,
294 ) -> &'a chalk_ir::ProgramClauseData<Self> { 294 ) -> &'a chalk_ir::ProgramClauseData<Self> {
295 clause 295 clause
296 } 296 }
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs
index fe62f3fa7..d6bacba1d 100644
--- a/crates/hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/hir_ty/src/traits/chalk/mapping.rs
@@ -464,6 +464,18 @@ impl ToChalk for hir_def::ImplId {
464 } 464 }
465} 465}
466 466
467impl ToChalk for hir_def::AdtId {
468 type Chalk = AdtId;
469
470 fn to_chalk(self, _db: &dyn HirDatabase) -> Self::Chalk {
471 chalk_ir::AdtId(self.into())
472 }
473
474 fn from_chalk(_db: &dyn HirDatabase, id: AdtId) -> Self {
475 id.0
476 }
477}
478
467impl ToChalk for CallableDefId { 479impl ToChalk for CallableDefId {
468 type Chalk = FnDefId; 480 type Chalk = FnDefId;
469 481
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
index 33bed6991..daea2aa95 100644
--- a/crates/ide/src/completion.rs
+++ b/crates/ide/src/completion.rs
@@ -19,6 +19,7 @@ mod complete_unqualified_path;
19mod complete_postfix; 19mod complete_postfix;
20mod complete_macro_in_item_position; 20mod complete_macro_in_item_position;
21mod complete_trait_impl; 21mod complete_trait_impl;
22mod complete_mod;
22 23
23use ide_db::RootDatabase; 24use ide_db::RootDatabase;
24 25
@@ -124,6 +125,7 @@ pub(crate) fn completions(
124 complete_postfix::complete_postfix(&mut acc, &ctx); 125 complete_postfix::complete_postfix(&mut acc, &ctx);
125 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 126 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
126 complete_trait_impl::complete_trait_impl(&mut acc, &ctx); 127 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
128 complete_mod::complete_mod(&mut acc, &ctx);
127 129
128 Some(acc) 130 Some(acc)
129} 131}
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs
index 0abfaebcb..f4a9864d1 100644
--- a/crates/ide/src/completion/complete_attribute.rs
+++ b/crates/ide/src/completion/complete_attribute.rs
@@ -13,6 +13,10 @@ use crate::completion::{
13}; 13};
14 14
15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16 if ctx.mod_declaration_under_caret.is_some() {
17 return None;
18 }
19
16 let attribute = ctx.attribute_under_caret.as_ref()?; 20 let attribute = ctx.attribute_under_caret.as_ref()?;
17 match (attribute.path(), attribute.token_tree()) { 21 match (attribute.path(), attribute.token_tree()) {
18 (Some(path), Some(token_tree)) if path.to_string() == "derive" => { 22 (Some(path), Some(token_tree)) if path.to_string() == "derive" => {
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/ide/src/completion/complete_mod.rs
new file mode 100644
index 000000000..3cfc2e131
--- /dev/null
+++ b/crates/ide/src/completion/complete_mod.rs
@@ -0,0 +1,324 @@
1//! Completes mod declarations.
2
3use base_db::{SourceDatabaseExt, VfsPath};
4use hir::{Module, ModuleSource};
5use ide_db::RootDatabase;
6use rustc_hash::FxHashSet;
7
8use crate::{CompletionItem, CompletionItemKind};
9
10use super::{
11 completion_context::CompletionContext, completion_item::CompletionKind,
12 completion_item::Completions,
13};
14
15/// Complete mod declaration, i.e. `mod <|> ;`
16pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
17 let mod_under_caret = match &ctx.mod_declaration_under_caret {
18 Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
19 Some(mod_under_caret) => mod_under_caret,
20 None => return None,
21 };
22
23 let _p = profile::span("completion::complete_mod");
24
25 let current_module = ctx.scope.module()?;
26
27 let module_definition_file =
28 current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
29 let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
30 let directory_to_look_for_submodules = directory_to_look_for_submodules(
31 current_module,
32 ctx.db,
33 source_root.path_for_file(&module_definition_file)?,
34 )?;
35
36 let existing_mod_declarations = current_module
37 .children(ctx.db)
38 .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
39 .collect::<FxHashSet<_>>();
40
41 let module_declaration_file =
42 current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
43 module_declaration_source_file.file_id.original_file(ctx.db)
44 });
45
46 source_root
47 .iter()
48 .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
49 .filter(|submodule_candidate_file| {
50 Some(submodule_candidate_file) != module_declaration_file.as_ref()
51 })
52 .filter_map(|submodule_file| {
53 let submodule_path = source_root.path_for_file(&submodule_file)?;
54 let directory_with_submodule = submodule_path.parent()?;
55 match submodule_path.name_and_extension()? {
56 ("lib", Some("rs")) | ("main", Some("rs")) => None,
57 ("mod", Some("rs")) => {
58 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
59 match directory_with_submodule.name_and_extension()? {
60 (directory_name, None) => Some(directory_name.to_owned()),
61 _ => None,
62 }
63 } else {
64 None
65 }
66 }
67 (file_name, Some("rs"))
68 if directory_with_submodule == directory_to_look_for_submodules =>
69 {
70 Some(file_name.to_owned())
71 }
72 _ => None,
73 }
74 })
75 .filter(|name| !existing_mod_declarations.contains(name))
76 .for_each(|submodule_name| {
77 let mut label = submodule_name;
78 if mod_under_caret.semicolon_token().is_none() {
79 label.push(';')
80 }
81 acc.add(
82 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
83 .kind(CompletionItemKind::Module),
84 )
85 });
86
87 Some(())
88}
89
90fn directory_to_look_for_submodules(
91 module: Module,
92 db: &RootDatabase,
93 module_file_path: &VfsPath,
94) -> Option<VfsPath> {
95 let directory_with_module_path = module_file_path.parent()?;
96 let base_directory = match module_file_path.name_and_extension()? {
97 ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => {
98 Some(directory_with_module_path)
99 }
100 (regular_rust_file_name, Some("rs")) => {
101 if matches!(
102 (
103 directory_with_module_path
104 .parent()
105 .as_ref()
106 .and_then(|path| path.name_and_extension()),
107 directory_with_module_path.name_and_extension(),
108 ),
109 (Some(("src", None)), Some(("bin", None)))
110 ) {
111 // files in /src/bin/ can import each other directly
112 Some(directory_with_module_path)
113 } else {
114 directory_with_module_path.join(regular_rust_file_name)
115 }
116 }
117 _ => None,
118 }?;
119
120 let mut resulting_path = base_directory;
121 for module in module_chain_to_containing_module_file(module, db) {
122 if let Some(name) = module.name(db) {
123 resulting_path = resulting_path.join(&name.to_string())?;
124 }
125 }
126
127 Some(resulting_path)
128}
129
130fn module_chain_to_containing_module_file(
131 current_module: Module,
132 db: &RootDatabase,
133) -> Vec<Module> {
134 let mut path = Vec::new();
135
136 let mut current_module = Some(current_module);
137 while let Some(ModuleSource::Module(_)) =
138 current_module.map(|module| module.definition_source(db).value)
139 {
140 if let Some(module) = current_module {
141 path.insert(0, module);
142 current_module = module.parent(db);
143 } else {
144 current_module = None;
145 }
146 }
147
148 path
149}
150
151#[cfg(test)]
152mod tests {
153 use crate::completion::{test_utils::completion_list, CompletionKind};
154 use expect_test::{expect, Expect};
155
156 fn check(ra_fixture: &str, expect: Expect) {
157 let actual = completion_list(ra_fixture, CompletionKind::Magic);
158 expect.assert_eq(&actual);
159 }
160
161 #[test]
162 fn lib_module_completion() {
163 check(
164 r#"
165 //- /lib.rs
166 mod <|>
167 //- /foo.rs
168 fn foo() {}
169 //- /foo/ignored_foo.rs
170 fn ignored_foo() {}
171 //- /bar/mod.rs
172 fn bar() {}
173 //- /bar/ignored_bar.rs
174 fn ignored_bar() {}
175 "#,
176 expect![[r#"
177 md bar;
178 md foo;
179 "#]],
180 );
181 }
182
183 #[test]
184 fn no_module_completion_with_module_body() {
185 check(
186 r#"
187 //- /lib.rs
188 mod <|> {
189
190 }
191 //- /foo.rs
192 fn foo() {}
193 "#,
194 expect![[r#""#]],
195 );
196 }
197
198 #[test]
199 fn main_module_completion() {
200 check(
201 r#"
202 //- /main.rs
203 mod <|>
204 //- /foo.rs
205 fn foo() {}
206 //- /foo/ignored_foo.rs
207 fn ignored_foo() {}
208 //- /bar/mod.rs
209 fn bar() {}
210 //- /bar/ignored_bar.rs
211 fn ignored_bar() {}
212 "#,
213 expect![[r#"
214 md bar;
215 md foo;
216 "#]],
217 );
218 }
219
220 #[test]
221 fn main_test_module_completion() {
222 check(
223 r#"
224 //- /main.rs
225 mod tests {
226 mod <|>;
227 }
228 //- /tests/foo.rs
229 fn foo() {}
230 "#,
231 expect![[r#"
232 md foo
233 "#]],
234 );
235 }
236
237 #[test]
238 fn directly_nested_module_completion() {
239 check(
240 r#"
241 //- /lib.rs
242 mod foo;
243 //- /foo.rs
244 mod <|>;
245 //- /foo/bar.rs
246 fn bar() {}
247 //- /foo/bar/ignored_bar.rs
248 fn ignored_bar() {}
249 //- /foo/baz/mod.rs
250 fn baz() {}
251 //- /foo/moar/ignored_moar.rs
252 fn ignored_moar() {}
253 "#,
254 expect![[r#"
255 md bar
256 md baz
257 "#]],
258 );
259 }
260
261 #[test]
262 fn nested_in_source_module_completion() {
263 check(
264 r#"
265 //- /lib.rs
266 mod foo;
267 //- /foo.rs
268 mod bar {
269 mod <|>
270 }
271 //- /foo/bar/baz.rs
272 fn baz() {}
273 "#,
274 expect![[r#"
275 md baz;
276 "#]],
277 );
278 }
279
280 // FIXME binary modules are not supported in tests properly
281 // Binary modules are a bit special, they allow importing the modules from `/src/bin`
282 // and that's why are good to test two things:
283 // * no cycles are allowed in mod declarations
284 // * no modules from the parent directory are proposed
285 // Unfortunately, binary modules support is in cargo not rustc,
286 // hence the test does not work now
287 //
288 // #[test]
289 // fn regular_bin_module_completion() {
290 // check(
291 // r#"
292 // //- /src/bin.rs
293 // fn main() {}
294 // //- /src/bin/foo.rs
295 // mod <|>
296 // //- /src/bin/bar.rs
297 // fn bar() {}
298 // //- /src/bin/bar/bar_ignored.rs
299 // fn bar_ignored() {}
300 // "#,
301 // expect![[r#"
302 // md bar;
303 // "#]],
304 // );
305 // }
306
307 #[test]
308 fn already_declared_bin_module_completion_omitted() {
309 check(
310 r#"
311 //- /src/bin.rs
312 fn main() {}
313 //- /src/bin/foo.rs
314 mod <|>
315 //- /src/bin/bar.rs
316 mod foo;
317 fn bar() {}
318 //- /src/bin/bar/bar_ignored.rs
319 fn bar_ignored() {}
320 "#,
321 expect![[r#""#]],
322 );
323 }
324}
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs
index accb09f7e..79de50792 100644
--- a/crates/ide/src/completion/complete_qualified_path.rs
+++ b/crates/ide/src/completion/complete_qualified_path.rs
@@ -13,7 +13,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
13 None => return, 13 None => return,
14 }; 14 };
15 15
16 if ctx.attribute_under_caret.is_some() { 16 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
17 return; 17 return;
18 } 18 }
19 19
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs
index 1f1b682a7..8eda4b64d 100644
--- a/crates/ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ide/src/completion/complete_unqualified_path.rs
@@ -13,6 +13,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
13 if ctx.record_lit_syntax.is_some() 13 if ctx.record_lit_syntax.is_some()
14 || ctx.record_pat_syntax.is_some() 14 || ctx.record_pat_syntax.is_some()
15 || ctx.attribute_under_caret.is_some() 15 || ctx.attribute_under_caret.is_some()
16 || ctx.mod_declaration_under_caret.is_some()
16 { 17 {
17 return; 18 return;
18 } 19 }
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs
index 47355d5dc..161f59c1e 100644
--- a/crates/ide/src/completion/completion_context.rs
+++ b/crates/ide/src/completion/completion_context.rs
@@ -77,6 +77,7 @@ pub(crate) struct CompletionContext<'a> {
77 pub(super) is_path_type: bool, 77 pub(super) is_path_type: bool,
78 pub(super) has_type_args: bool, 78 pub(super) has_type_args: bool,
79 pub(super) attribute_under_caret: Option<ast::Attr>, 79 pub(super) attribute_under_caret: Option<ast::Attr>,
80 pub(super) mod_declaration_under_caret: Option<ast::Module>,
80 pub(super) unsafe_is_prev: bool, 81 pub(super) unsafe_is_prev: bool,
81 pub(super) if_is_prev: bool, 82 pub(super) if_is_prev: bool,
82 pub(super) block_expr_parent: bool, 83 pub(super) block_expr_parent: bool,
@@ -152,6 +153,7 @@ impl<'a> CompletionContext<'a> {
152 has_type_args: false, 153 has_type_args: false,
153 dot_receiver_is_ambiguous_float_literal: false, 154 dot_receiver_is_ambiguous_float_literal: false,
154 attribute_under_caret: None, 155 attribute_under_caret: None,
156 mod_declaration_under_caret: None,
155 unsafe_is_prev: false, 157 unsafe_is_prev: false,
156 in_loop_body: false, 158 in_loop_body: false,
157 ref_pat_parent: false, 159 ref_pat_parent: false,
@@ -238,7 +240,10 @@ impl<'a> CompletionContext<'a> {
238 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); 240 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
239 self.is_match_arm = is_match_arm(syntax_element.clone()); 241 self.is_match_arm = is_match_arm(syntax_element.clone());
240 self.has_item_list_or_source_file_parent = 242 self.has_item_list_or_source_file_parent =
241 has_item_list_or_source_file_parent(syntax_element); 243 has_item_list_or_source_file_parent(syntax_element.clone());
244 self.mod_declaration_under_caret =
245 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
246 .filter(|module| module.item_list().is_none());
242 } 247 }
243 248
244 fn fill( 249 fn fill(
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs
index c6ae589db..b17ddf133 100644
--- a/crates/ide/src/completion/patterns.rs
+++ b/crates/ide/src/completion/patterns.rs
@@ -115,6 +115,7 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
115 .filter(|it| it.kind() == IF_KW) 115 .filter(|it| it.kind() == IF_KW)
116 .is_some() 116 .is_some()
117} 117}
118
118#[test] 119#[test]
119fn test_if_is_prev() { 120fn test_if_is_prev() {
120 check_pattern_is_applicable(r"if l<|>", if_is_prev); 121 check_pattern_is_applicable(r"if l<|>", if_is_prev);
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 25d6f7abd..d9fc25d88 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -4,7 +4,7 @@ mod injection;
4#[cfg(test)] 4#[cfg(test)]
5mod tests; 5mod tests;
6 6
7use hir::{Name, Semantics, VariantDef}; 7use hir::{Local, Name, Semantics, VariantDef};
8use ide_db::{ 8use ide_db::{
9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
10 RootDatabase, 10 RootDatabase,
@@ -13,8 +13,8 @@ use rustc_hash::FxHashMap;
13use syntax::{ 13use syntax::{
14 ast::{self, HasFormatSpecifier}, 14 ast::{self, HasFormatSpecifier},
15 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 15 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
16 SyntaxKind::*, 16 SyntaxKind::{self, *},
17 TextRange, WalkEvent, T, 17 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
18}; 18};
19 19
20use crate::FileId; 20use crate::FileId;
@@ -454,6 +454,32 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
454 Some(TextRange::new(range_start, range_end)) 454 Some(TextRange::new(range_start, range_end))
455} 455}
456 456
457/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
458fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
459 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
460 if parent.kind() != *kind {
461 return false;
462 }
463
464 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
465 // in the same pattern is unstable: rust-lang/rust#68354.
466 node = node.parent().unwrap().into();
467 kinds = rest;
468 }
469
470 // Only true if we matched all expected kinds
471 kinds.len() == 0
472}
473
474fn is_consumed_lvalue(
475 node: NodeOrToken<SyntaxNode, SyntaxToken>,
476 local: &Local,
477 db: &RootDatabase,
478) -> bool {
479 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
480 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
481}
482
457fn highlight_element( 483fn highlight_element(
458 sema: &Semantics<RootDatabase>, 484 sema: &Semantics<RootDatabase>,
459 bindings_shadow_count: &mut FxHashMap<Name, u32>, 485 bindings_shadow_count: &mut FxHashMap<Name, u32>,
@@ -522,6 +548,12 @@ fn highlight_element(
522 548
523 let mut h = highlight_def(db, def); 549 let mut h = highlight_def(db, def);
524 550
551 if let Definition::Local(local) = &def {
552 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
553 h |= HighlightModifier::Consuming;
554 }
555 }
556
525 if let Some(parent) = name_ref.syntax().parent() { 557 if let Some(parent) = name_ref.syntax().parent() {
526 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { 558 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
527 if let Definition::Field(field) = def { 559 if let Definition::Field(field) = def {
@@ -645,21 +677,30 @@ fn highlight_element(
645 .and_then(ast::SelfParam::cast) 677 .and_then(ast::SelfParam::cast)
646 .and_then(|p| p.mut_token()) 678 .and_then(|p| p.mut_token())
647 .is_some(); 679 .is_some();
648 // closure to enforce lazyness 680 let self_path = &element
649 let self_path = || { 681 .parent()
650 sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?) 682 .as_ref()
651 }; 683 .and_then(SyntaxNode::parent)
684 .and_then(ast::Path::cast)
685 .and_then(|p| sema.resolve_path(&p));
686 let mut h = HighlightTag::SelfKeyword.into();
652 if self_param_is_mut 687 if self_param_is_mut
653 || matches!(self_path(), 688 || matches!(self_path,
654 Some(hir::PathResolution::Local(local)) 689 Some(hir::PathResolution::Local(local))
655 if local.is_self(db) 690 if local.is_self(db)
656 && (local.is_mut(db) || local.ty(db).is_mutable_reference()) 691 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
657 ) 692 )
658 { 693 {
659 HighlightTag::SelfKeyword | HighlightModifier::Mutable 694 h |= HighlightModifier::Mutable
660 } else {
661 HighlightTag::SelfKeyword.into()
662 } 695 }
696
697 if let Some(hir::PathResolution::Local(local)) = self_path {
698 if is_consumed_lvalue(element, &local, db) {
699 h |= HighlightModifier::Consuming;
700 }
701 }
702
703 h
663 } 704 }
664 T![ref] => element 705 T![ref] => element
665 .parent() 706 .parent()
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index d0df2e0ec..cde42024c 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -61,8 +61,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
61<span class="punctuation">}</span> 61<span class="punctuation">}</span>
62 62
63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
65 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> 65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span>
66 <span class="punctuation">}</span> 66 <span class="punctuation">}</span>
67 67
68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
@@ -80,8 +80,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
80<span class="punctuation">}</span> 80<span class="punctuation">}</span>
81 81
82<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> 82<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span>
83 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span> 83 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">FooCopy</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
84 <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span> 84 <span class="value_param">f</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span>
85 <span class="punctuation">}</span> 85 <span class="punctuation">}</span>
86 86
87 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 87 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
@@ -144,14 +144,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
144 <span class="variable">y</span><span class="punctuation">;</span> 144 <span class="variable">y</span><span class="punctuation">;</span>
145 145
146 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> 146 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
147 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="unresolved_reference">clone</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
147 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 148 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
148 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 149 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
149 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 150 <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="variable consuming">foo2</span><span class="punctuation">)</span><span class="punctuation">;</span>
150 151
151 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span> 152 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
152 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 153 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
153 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 154 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
154 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 155 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span>
155<span class="punctuation">}</span> 156<span class="punctuation">}</span>
156 157
157<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span> 158<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 6f72a29bd..57d4e1252 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -35,8 +35,8 @@ impl Bar for Foo {
35} 35}
36 36
37impl Foo { 37impl Foo {
38 fn baz(mut self) -> i32 { 38 fn baz(mut self, f: Foo) -> i32 {
39 self.x 39 f.baz(self)
40 } 40 }
41 41
42 fn qux(&mut self) { 42 fn qux(&mut self) {
@@ -54,8 +54,8 @@ struct FooCopy {
54} 54}
55 55
56impl FooCopy { 56impl FooCopy {
57 fn baz(self) -> u32 { 57 fn baz(self, f: FooCopy) -> u32 {
58 self.x 58 f.baz(self)
59 } 59 }
60 60
61 fn qux(&mut self) { 61 fn qux(&mut self) {
@@ -118,14 +118,15 @@ fn main() {
118 y; 118 y;
119 119
120 let mut foo = Foo { x, y: x }; 120 let mut foo = Foo { x, y: x };
121 let foo2 = foo.clone();
121 foo.quop(); 122 foo.quop();
122 foo.qux(); 123 foo.qux();
123 foo.baz(); 124 foo.baz(foo2);
124 125
125 let mut copy = FooCopy { x }; 126 let mut copy = FooCopy { x };
126 copy.quop(); 127 copy.quop();
127 copy.qux(); 128 copy.qux();
128 copy.baz(); 129 copy.baz(copy);
129} 130}
130 131
131enum Option<T> { 132enum Option<T> {
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index 0d0affc27..f8c7aa491 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -157,9 +157,9 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
157 ast::IdentPat(it) => { 157 ast::IdentPat(it) => {
158 let local = sema.to_def(&it)?; 158 let local = sema.to_def(&it)?;
159 159
160 if let Some(record_field_pat) = it.syntax().parent().and_then(ast::RecordPatField::cast) { 160 if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
161 if record_field_pat.name_ref().is_none() { 161 if record_pat_field.name_ref().is_none() {
162 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) { 162 if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
163 let field = Definition::Field(field); 163 let field = Definition::Field(field);
164 return Some(NameClass::FieldShorthand { local, field }); 164 return Some(NameClass::FieldShorthand { local, field });
165 } 165 }
@@ -275,8 +275,8 @@ pub fn classify_name_ref(
275 } 275 }
276 } 276 }
277 277
278 if let Some(record_field_pat) = ast::RecordPatField::cast(parent.clone()) { 278 if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) {
279 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) { 279 if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
280 let field = Definition::Field(field); 280 let field = Definition::Field(field);
281 return Some(NameRefClass::Definition(field)); 281 return Some(NameRefClass::Definition(field));
282 } 282 }
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index 796f206e1..7e7f73dee 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -188,7 +188,7 @@ fn tuple_pat_fields(p: &mut Parser) {
188 p.expect(T![')']); 188 p.expect(T![')']);
189} 189}
190 190
191// test record_field_pat_list 191// test record_pat_field_list
192// fn foo() { 192// fn foo() {
193// let S {} = (); 193// let S {} = ();
194// let S { f, ref mut g } = (); 194// let S { f, ref mut g } = ();
@@ -208,7 +208,7 @@ fn record_pat_field_list(p: &mut Parser) {
208 c => { 208 c => {
209 let m = p.start(); 209 let m = p.start();
210 match c { 210 match c {
211 // test record_field_pat 211 // test record_pat_field
212 // fn foo() { 212 // fn foo() {
213 // let S { 0: 1 } = (); 213 // let S { 0: 1 } = ();
214 // let S { x: 1 } = (); 214 // let S { x: 1 } = ();
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index 2d91939ce..288c39e49 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -33,7 +33,7 @@ pub enum ProjectWorkspace {
33 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 33 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
34 Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, 34 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
35 /// Project workspace was manually specified using a `rust-project.json` file. 35 /// Project workspace was manually specified using a `rust-project.json` file.
36 Json { project: ProjectJson }, 36 Json { project: ProjectJson, sysroot: Option<Sysroot> },
37} 37}
38 38
39impl fmt::Debug for ProjectWorkspace { 39impl fmt::Debug for ProjectWorkspace {
@@ -44,10 +44,10 @@ impl fmt::Debug for ProjectWorkspace {
44 .field("n_packages", &cargo.packages().len()) 44 .field("n_packages", &cargo.packages().len())
45 .field("n_sysroot_crates", &sysroot.crates().len()) 45 .field("n_sysroot_crates", &sysroot.crates().len())
46 .finish(), 46 .finish(),
47 ProjectWorkspace::Json { project } => { 47 ProjectWorkspace::Json { project, sysroot } => {
48 let mut debug_struct = f.debug_struct("Json"); 48 let mut debug_struct = f.debug_struct("Json");
49 debug_struct.field("n_crates", &project.n_crates()); 49 debug_struct.field("n_crates", &project.n_crates());
50 if let Some(sysroot) = &project.sysroot { 50 if let Some(sysroot) = sysroot {
51 debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); 51 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
52 } 52 }
53 debug_struct.finish() 53 debug_struct.finish()
@@ -169,7 +169,11 @@ impl ProjectWorkspace {
169 })?; 169 })?;
170 let project_location = project_json.parent().unwrap().to_path_buf(); 170 let project_location = project_json.parent().unwrap().to_path_buf();
171 let project = ProjectJson::new(&project_location, data); 171 let project = ProjectJson::new(&project_location, data);
172 ProjectWorkspace::Json { project } 172 let sysroot = match &project.sysroot_src {
173 Some(path) => Some(Sysroot::load(path)?),
174 None => None,
175 };
176 ProjectWorkspace::Json { project, sysroot }
173 } 177 }
174 ProjectManifest::CargoToml(cargo_toml) => { 178 ProjectManifest::CargoToml(cargo_toml) => {
175 let cargo_version = utf8_stdout({ 179 let cargo_version = utf8_stdout({
@@ -203,12 +207,21 @@ impl ProjectWorkspace {
203 Ok(res) 207 Ok(res)
204 } 208 }
205 209
210 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
211 let sysroot = match &project_json.sysroot_src {
212 Some(path) => Some(Sysroot::load(path)?),
213 None => None,
214 };
215
216 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
217 }
218
206 /// Returns the roots for the current `ProjectWorkspace` 219 /// Returns the roots for the current `ProjectWorkspace`
207 /// The return type contains the path and whether or not 220 /// The return type contains the path and whether or not
208 /// the root is a member of the current workspace 221 /// the root is a member of the current workspace
209 pub fn to_roots(&self) -> Vec<PackageRoot> { 222 pub fn to_roots(&self) -> Vec<PackageRoot> {
210 match self { 223 match self {
211 ProjectWorkspace::Json { project } => project 224 ProjectWorkspace::Json { project, sysroot } => project
212 .crates() 225 .crates()
213 .map(|(_, krate)| PackageRoot { 226 .map(|(_, krate)| PackageRoot {
214 is_member: krate.is_workspace_member, 227 is_member: krate.is_workspace_member,
@@ -217,7 +230,7 @@ impl ProjectWorkspace {
217 }) 230 })
218 .collect::<FxHashSet<_>>() 231 .collect::<FxHashSet<_>>()
219 .into_iter() 232 .into_iter()
220 .chain(project.sysroot.as_ref().into_iter().flat_map(|sysroot| { 233 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
221 sysroot.crates().map(move |krate| PackageRoot { 234 sysroot.crates().map(move |krate| PackageRoot {
222 is_member: false, 235 is_member: false,
223 include: vec![sysroot[krate].root_dir().to_path_buf()], 236 include: vec![sysroot[krate].root_dir().to_path_buf()],
@@ -255,7 +268,7 @@ impl ProjectWorkspace {
255 268
256 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> { 269 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
257 match self { 270 match self {
258 ProjectWorkspace::Json { project } => project 271 ProjectWorkspace::Json { project, sysroot: _ } => project
259 .crates() 272 .crates()
260 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) 273 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
261 .cloned() 274 .cloned()
@@ -285,9 +298,8 @@ impl ProjectWorkspace {
285 ) -> CrateGraph { 298 ) -> CrateGraph {
286 let mut crate_graph = CrateGraph::default(); 299 let mut crate_graph = CrateGraph::default();
287 match self { 300 match self {
288 ProjectWorkspace::Json { project } => { 301 ProjectWorkspace::Json { project, sysroot } => {
289 let sysroot_dps = project 302 let sysroot_dps = sysroot
290 .sysroot
291 .as_ref() 303 .as_ref()
292 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load)); 304 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
293 305
diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs
index 5a0fe749a..979e90058 100644
--- a/crates/project_model/src/project_json.rs
+++ b/crates/project_model/src/project_json.rs
@@ -7,12 +7,12 @@ use paths::{AbsPath, AbsPathBuf};
7use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
8use serde::{de, Deserialize}; 8use serde::{de, Deserialize};
9 9
10use crate::{cfg_flag::CfgFlag, Sysroot}; 10use crate::cfg_flag::CfgFlag;
11 11
12/// Roots and crates that compose this Rust project. 12/// Roots and crates that compose this Rust project.
13#[derive(Clone, Debug, Eq, PartialEq)] 13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct ProjectJson { 14pub struct ProjectJson {
15 pub(crate) sysroot: Option<Sysroot>, 15 pub(crate) sysroot_src: Option<AbsPathBuf>,
16 crates: Vec<Crate>, 16 crates: Vec<Crate>,
17} 17}
18 18
@@ -35,7 +35,7 @@ pub struct Crate {
35impl ProjectJson { 35impl ProjectJson {
36 pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { 36 pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
37 ProjectJson { 37 ProjectJson {
38 sysroot: data.sysroot_src.map(|it| base.join(it)).map(|it| Sysroot::load(&it)), 38 sysroot_src: data.sysroot_src.map(|it| base.join(it)),
39 crates: data 39 crates: data
40 .crates 40 .crates
41 .into_iter() 41 .into_iter()
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index 74c0eda9a..871808d89 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -51,11 +51,11 @@ impl Sysroot {
51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { 51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52 let current_dir = cargo_toml.parent().unwrap(); 52 let current_dir = cargo_toml.parent().unwrap();
53 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; 53 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?;
54 let res = Sysroot::load(&sysroot_src_dir); 54 let res = Sysroot::load(&sysroot_src_dir)?;
55 Ok(res) 55 Ok(res)
56 } 56 }
57 57
58 pub fn load(sysroot_src_dir: &AbsPath) -> Sysroot { 58 pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
59 let mut sysroot = Sysroot { crates: Arena::default() }; 59 let mut sysroot = Sysroot { crates: Arena::default() };
60 60
61 for name in SYSROOT_CRATES.trim().lines() { 61 for name in SYSROOT_CRATES.trim().lines() {
@@ -89,7 +89,14 @@ impl Sysroot {
89 } 89 }
90 } 90 }
91 91
92 sysroot 92 if sysroot.by_name("core").is_none() {
93 anyhow::bail!(
94 "could not find libcore in sysroot path `{}`",
95 sysroot_src_dir.as_ref().display()
96 );
97 }
98
99 Ok(sysroot)
93 } 100 }
94 101
95 fn by_name(&self, name: &str) -> Option<SysrootCrate> { 102 fn by_name(&self, name: &str) -> Option<SysrootCrate> {
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index 89dae7d5a..00e8da8a7 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -44,4 +44,49 @@
44 }, 44 },
45 fixes: [], 45 fixes: [],
46 }, 46 },
47 MappedRustDiagnostic {
48 url: "file:///test/crates/hir_def/src/path.rs",
49 diagnostic: Diagnostic {
50 range: Range {
51 start: Position {
52 line: 264,
53 character: 8,
54 },
55 end: Position {
56 line: 264,
57 character: 76,
58 },
59 },
60 severity: Some(
61 Error,
62 ),
63 code: None,
64 source: Some(
65 "rustc",
66 ),
67 message: "Please register your known path in the path module",
68 related_information: Some(
69 [
70 DiagnosticRelatedInformation {
71 location: Location {
72 uri: "file:///test/crates/hir_def/src/data.rs",
73 range: Range {
74 start: Position {
75 line: 79,
76 character: 15,
77 },
78 end: Position {
79 line: 79,
80 character: 41,
81 },
82 },
83 },
84 message: "Exact error occured here",
85 },
86 ],
87 ),
88 tags: None,
89 },
90 fixes: [],
91 },
47] 92]
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index f69a949f2..33606edda 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -225,12 +225,43 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
225 225
226 // If error occurs from macro expansion, add related info pointing to 226 // If error occurs from macro expansion, add related info pointing to
227 // where the error originated 227 // where the error originated
228 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { 228 // Also, we would generate an additional diagnostic, so that exact place of macro
229 related_information.push(lsp_types::DiagnosticRelatedInformation { 229 // will be highlighted in the error origin place.
230 location: location_naive(workspace_root, &primary_span), 230 let additional_diagnostic =
231 message: "Error originated from macro here".to_string(), 231 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
232 }); 232 let in_macro_location = location_naive(workspace_root, &primary_span);
233 } 233
234 // Add related information for the main disagnostic.
235 related_information.push(lsp_types::DiagnosticRelatedInformation {
236 location: in_macro_location.clone(),
237 message: "Error originated from macro here".to_string(),
238 });
239
240 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
241 let information_for_additional_diagnostic =
242 vec![lsp_types::DiagnosticRelatedInformation {
243 location: location.clone(),
244 message: "Exact error occured here".to_string(),
245 }];
246
247 let diagnostic = lsp_types::Diagnostic {
248 range: in_macro_location.range,
249 severity,
250 code: code.clone().map(lsp_types::NumberOrString::String),
251 source: Some(source.clone()),
252 message: message.clone(),
253 related_information: Some(information_for_additional_diagnostic),
254 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
255 };
256
257 Some(MappedRustDiagnostic {
258 url: in_macro_location.uri,
259 diagnostic,
260 fixes: fixes.clone(),
261 })
262 } else {
263 None
264 };
234 265
235 let diagnostic = lsp_types::Diagnostic { 266 let diagnostic = lsp_types::Diagnostic {
236 range: location.range, 267 range: location.range,
@@ -246,8 +277,14 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
246 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 277 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
247 }; 278 };
248 279
249 MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() } 280 let main_diagnostic =
281 MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() };
282 match additional_diagnostic {
283 None => vec![main_diagnostic],
284 Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic],
285 }
250 }) 286 })
287 .flatten()
251 .collect() 288 .collect()
252} 289}
253 290
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 20019b944..bab6f8a71 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -109,7 +109,7 @@ impl GlobalState {
109 ) 109 )
110 } 110 }
111 LinkedProject::InlineJsonProject(it) => { 111 LinkedProject::InlineJsonProject(it) => {
112 Ok(project_model::ProjectWorkspace::Json { project: it.clone() }) 112 project_model::ProjectWorkspace::load_inline(it.clone())
113 } 113 }
114 }) 114 })
115 .collect::<Vec<_>>(); 115 .collect::<Vec<_>>();
diff --git a/crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rast b/crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rast
index 866e60ed8..866e60ed8 100644
--- a/crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rast
diff --git a/crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rs b/crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rs
index da3412fa8..da3412fa8 100644
--- a/crates/syntax/test_data/parser/inline/ok/0102_record_field_pat_list.rs
+++ b/crates/syntax/test_data/parser/inline/ok/0102_record_pat_field_list.rs
diff --git a/crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rast b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast
index 925409bdf..925409bdf 100644
--- a/crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast
diff --git a/crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rs b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs
index 26b1d5f89..26b1d5f89 100644
--- a/crates/syntax/test_data/parser/inline/ok/0145_record_field_pat.rs
+++ b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index e9196fcd2..4aa2d6526 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -23,13 +23,22 @@ impl FileSet {
23 let mut base = self.paths[&anchor].clone(); 23 let mut base = self.paths[&anchor].clone();
24 base.pop(); 24 base.pop();
25 let path = base.join(path)?; 25 let path = base.join(path)?;
26 let res = self.files.get(&path).copied(); 26 self.files.get(&path).copied()
27 res 27 }
28
29 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
30 self.files.get(path)
28 } 31 }
32
33 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
34 self.paths.get(file)
35 }
36
29 pub fn insert(&mut self, file_id: FileId, path: VfsPath) { 37 pub fn insert(&mut self, file_id: FileId, path: VfsPath) {
30 self.files.insert(path.clone(), file_id); 38 self.files.insert(path.clone(), file_id);
31 self.paths.insert(file_id, path); 39 self.paths.insert(file_id, path);
32 } 40 }
41
33 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { 42 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
34 self.paths.keys().copied() 43 self.paths.keys().copied()
35 } 44 }
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index 944a702df..022a0be1e 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -48,6 +48,24 @@ impl VfsPath {
48 (VfsPathRepr::VirtualPath(_), _) => false, 48 (VfsPathRepr::VirtualPath(_), _) => false,
49 } 49 }
50 } 50 }
51 pub fn parent(&self) -> Option<VfsPath> {
52 let mut parent = self.clone();
53 if parent.pop() {
54 Some(parent)
55 } else {
56 None
57 }
58 }
59
60 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
61 match &self.0 {
62 VfsPathRepr::PathBuf(p) => Some((
63 p.file_stem()?.to_str()?,
64 p.extension().and_then(|extension| extension.to_str()),
65 )),
66 VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
67 }
68 }
51 69
52 // Don't make this `pub` 70 // Don't make this `pub`
53 pub(crate) fn encode(&self, buf: &mut Vec<u8>) { 71 pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
@@ -268,4 +286,60 @@ impl VirtualPath {
268 res.0 = format!("{}/{}", res.0, path); 286 res.0 = format!("{}/{}", res.0, path);
269 Some(res) 287 Some(res)
270 } 288 }
289
290 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
291 let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
292 let file_name = match file_path.rfind('/') {
293 Some(position) => &file_path[position + 1..],
294 None => file_path,
295 };
296
297 if file_name.is_empty() {
298 None
299 } else {
300 let mut file_stem_and_extension = file_name.rsplitn(2, '.');
301 let extension = file_stem_and_extension.next();
302 let file_stem = file_stem_and_extension.next();
303
304 match (file_stem, extension) {
305 (None, None) => None,
306 (None, Some(_)) | (Some(""), Some(_)) => Some((file_name, None)),
307 (Some(file_stem), extension) => Some((file_stem, extension)),
308 }
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn virtual_path_extensions() {
319 assert_eq!(VirtualPath("/".to_string()).name_and_extension(), None);
320 assert_eq!(
321 VirtualPath("/directory".to_string()).name_and_extension(),
322 Some(("directory", None))
323 );
324 assert_eq!(
325 VirtualPath("/directory/".to_string()).name_and_extension(),
326 Some(("directory", None))
327 );
328 assert_eq!(
329 VirtualPath("/directory/file".to_string()).name_and_extension(),
330 Some(("file", None))
331 );
332 assert_eq!(
333 VirtualPath("/directory/.file".to_string()).name_and_extension(),
334 Some((".file", None))
335 );
336 assert_eq!(
337 VirtualPath("/directory/.file.rs".to_string()).name_and_extension(),
338 Some((".file", Some("rs")))
339 );
340 assert_eq!(
341 VirtualPath("/directory/file.rs".to_string()).name_and_extension(),
342 Some(("file", Some("rs")))
343 );
344 }
271} 345}