aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/code_model.rs2
-rw-r--r--crates/ra_hir/src/expr.rs279
-rw-r--r--crates/ra_hir/src/expr/validation.rs137
-rw-r--r--crates/ra_hir/src/source_binder.rs30
-rw-r--r--crates/ra_hir_def/src/body/scope.rs219
-rw-r--r--crates/ra_hir_def/src/nameres.rs9
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs3
7 files changed, 345 insertions, 334 deletions
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index dd43271f4..078bd8609 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -23,7 +23,7 @@ use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner};
23use crate::{ 23use crate::{
24 adt::VariantDef, 24 adt::VariantDef,
25 db::{AstDatabase, DefDatabase, HirDatabase}, 25 db::{AstDatabase, DefDatabase, HirDatabase},
26 expr::{validation::ExprValidator, BindingAnnotation, Body, BodySourceMap, Pat, PatId}, 26 expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId},
27 generics::{GenericDef, HasGenericParams}, 27 generics::{GenericDef, HasGenericParams},
28 ids::{ 28 ids::{
29 AstItemDef, ConstId, EnumId, FunctionId, MacroDefId, StaticId, StructId, TraitId, 29 AstItemDef, ConstId, EnumId, FunctionId, MacroDefId, StaticId, StructId, TraitId,
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index 899e0fa04..e3733779e 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -1,12 +1,19 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3pub(crate) mod validation;
4
5use std::sync::Arc; 3use std::sync::Arc;
6 4
5use hir_def::path::known;
6use hir_expand::diagnostics::DiagnosticSink;
7use ra_syntax::ast;
7use ra_syntax::AstPtr; 8use ra_syntax::AstPtr;
9use rustc_hash::FxHashSet;
8 10
9use crate::{db::HirDatabase, DefWithBody, HasBody, Resolver}; 11use crate::{
12 db::HirDatabase,
13 diagnostics::{MissingFields, MissingOkInTailExpr},
14 ty::{ApplicationTy, InferenceResult, Ty, TypeCtor},
15 Adt, DefWithBody, Function, HasBody, Name, Path, Resolver,
16};
10 17
11pub use hir_def::{ 18pub use hir_def::{
12 body::{ 19 body::{
@@ -43,191 +50,121 @@ pub(crate) fn resolver_for_scope(
43 r 50 r
44} 51}
45 52
46#[cfg(test)] 53pub(crate) struct ExprValidator<'a, 'b: 'a> {
47mod tests { 54 func: Function,
48 use hir_expand::Source; 55 infer: Arc<InferenceResult>,
49 use ra_db::{fixture::WithFixture, SourceDatabase}; 56 sink: &'a mut DiagnosticSink<'b>,
50 use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 57}
51 use test_utils::{assert_eq_text, extract_offset};
52
53 use crate::{source_binder::SourceAnalyzer, test_db::TestDB};
54
55 fn do_check(code: &str, expected: &[&str]) {
56 let (off, code) = extract_offset(code);
57 let code = {
58 let mut buf = String::new();
59 let off = u32::from(off) as usize;
60 buf.push_str(&code[..off]);
61 buf.push_str("marker");
62 buf.push_str(&code[off..]);
63 buf
64 };
65 58
66 let (db, file_id) = TestDB::with_single_file(&code); 59impl<'a, 'b> ExprValidator<'a, 'b> {
67 60 pub(crate) fn new(
68 let file = db.parse(file_id).ok().unwrap(); 61 func: Function,
69 let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap(); 62 infer: Arc<InferenceResult>,
70 let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None); 63 sink: &'a mut DiagnosticSink<'b>,
71 64 ) -> ExprValidator<'a, 'b> {
72 let scopes = analyzer.scopes(); 65 ExprValidator { func, infer, sink }
73 let expr_id = analyzer
74 .body_source_map()
75 .node_expr(Source { file_id: file_id.into(), ast: &marker.into() })
76 .unwrap();
77 let scope = scopes.scope_for(expr_id);
78
79 let actual = scopes
80 .scope_chain(scope)
81 .flat_map(|scope| scopes.entries(scope))
82 .map(|it| it.name().to_string())
83 .collect::<Vec<_>>()
84 .join("\n");
85 let expected = expected.join("\n");
86 assert_eq_text!(&expected, &actual);
87 } 66 }
88 67
89 #[test] 68 pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) {
90 fn test_lambda_scope() { 69 let body = self.func.body(db);
91 do_check(
92 r"
93 fn quux(foo: i32) {
94 let f = |bar, baz: i32| {
95 <|>
96 };
97 }",
98 &["bar", "baz", "foo"],
99 );
100 }
101 70
102 #[test] 71 for e in body.exprs() {
103 fn test_call_scope() { 72 if let (id, Expr::RecordLit { path, fields, spread }) = e {
104 do_check( 73 self.validate_record_literal(id, path, fields, *spread, db);
105 r" 74 }
106 fn quux() { 75 }
107 f(|x| <|> );
108 }",
109 &["x"],
110 );
111 }
112 76
113 #[test] 77 let body_expr = &body[body.body_expr()];
114 fn test_method_call_scope() { 78 if let Expr::Block { statements: _, tail: Some(t) } = body_expr {
115 do_check( 79 self.validate_results_in_tail_expr(body.body_expr(), *t, db);
116 r" 80 }
117 fn quux() {
118 z.f(|x| <|> );
119 }",
120 &["x"],
121 );
122 } 81 }
123 82
124 #[test] 83 fn validate_record_literal(
125 fn test_loop_scope() { 84 &mut self,
126 do_check( 85 id: ExprId,
127 r" 86 _path: &Option<Path>,
128 fn quux() { 87 fields: &[RecordLitField],
129 loop { 88 spread: Option<ExprId>,
130 let x = (); 89 db: &impl HirDatabase,
131 <|> 90 ) {
132 }; 91 if spread.is_some() {
133 }", 92 return;
134 &["x"], 93 }
135 ); 94
136 } 95 let struct_def = match self.infer[id].as_adt() {
96 Some((Adt::Struct(s), _)) => s,
97 _ => return,
98 };
137 99
138 #[test] 100 let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
139 fn test_match() { 101 let missed_fields: Vec<Name> = struct_def
140 do_check( 102 .fields(db)
141 r" 103 .iter()
142 fn quux() { 104 .filter_map(|f| {
143 match () { 105 let name = f.name(db);
144 Some(x) => { 106 if lit_fields.contains(&name) {
145 <|> 107 None
108 } else {
109 Some(name)
110 }
111 })
112 .collect();
113 if missed_fields.is_empty() {
114 return;
115 }
116 let source_map = self.func.body_source_map(db);
117
118 if let Some(source_ptr) = source_map.expr_syntax(id) {
119 if let Some(expr) = source_ptr.ast.a() {
120 let root = source_ptr.file_syntax(db);
121 if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
122 if let Some(field_list) = record_lit.record_field_list() {
123 self.sink.push(MissingFields {
124 file: source_ptr.file_id,
125 field_list: AstPtr::new(&field_list),
126 missed_fields,
127 })
146 } 128 }
147 }; 129 }
148 }", 130 }
149 &["x"], 131 }
150 );
151 }
152
153 #[test]
154 fn test_shadow_variable() {
155 do_check(
156 r"
157 fn foo(x: String) {
158 let x : &str = &x<|>;
159 }",
160 &["x"],
161 );
162 } 132 }
163 133
164 fn do_check_local_name(code: &str, expected_offset: u32) { 134 fn validate_results_in_tail_expr(
165 let (off, code) = extract_offset(code); 135 &mut self,
166 136 body_id: ExprId,
167 let (db, file_id) = TestDB::with_single_file(&code); 137 id: ExprId,
168 let file = db.parse(file_id).ok().unwrap(); 138 db: &impl HirDatabase,
169 let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()) 139 ) {
170 .expect("failed to find a name at the target offset"); 140 // the mismatch will be on the whole block currently
171 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); 141 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
172 let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None); 142 Some(m) => m,
143 None => return,
144 };
173 145
174 let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap(); 146 let std_result_path = known::std_result_result();
175 let local_name =
176 local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
177 assert_eq!(local_name.range(), expected_name.syntax().text_range());
178 }
179 147
180 #[test] 148 let resolver = self.func.resolver(db);
181 fn test_resolve_local_name() { 149 let std_result_enum = match resolver.resolve_known_enum(db, &std_result_path) {
182 do_check_local_name( 150 Some(it) => it,
183 r#" 151 _ => return,
184 fn foo(x: i32, y: u32) { 152 };
185 {
186 let z = x * 2;
187 }
188 {
189 let t = x<|> * 3;
190 }
191 }"#,
192 21,
193 );
194 }
195 153
196 #[test] 154 let std_result_ctor = TypeCtor::Adt(Adt::Enum(std_result_enum));
197 fn test_resolve_local_name_declaration() { 155 let params = match &mismatch.expected {
198 do_check_local_name( 156 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters,
199 r#" 157 _ => return,
200 fn foo(x: String) { 158 };
201 let x : &str = &x<|>;
202 }"#,
203 21,
204 );
205 }
206 159
207 #[test] 160 if params.len() == 2 && &params[0] == &mismatch.actual {
208 fn test_resolve_local_name_shadow() { 161 let source_map = self.func.body_source_map(db);
209 do_check_local_name(
210 r"
211 fn foo(x: String) {
212 let x : &str = &x;
213 x<|>
214 }
215 ",
216 53,
217 );
218 }
219 162
220 #[test] 163 if let Some(source_ptr) = source_map.expr_syntax(id) {
221 fn ref_patterns_contribute_bindings() { 164 if let Some(expr) = source_ptr.ast.a() {
222 do_check_local_name( 165 self.sink.push(MissingOkInTailExpr { file: source_ptr.file_id, expr });
223 r"
224 fn foo() {
225 if let Some(&from) = bar() {
226 from<|>;
227 } 166 }
228 } 167 }
229 ", 168 }
230 53,
231 );
232 } 169 }
233} 170}
diff --git a/crates/ra_hir/src/expr/validation.rs b/crates/ra_hir/src/expr/validation.rs
deleted file mode 100644
index 3054f1dce..000000000
--- a/crates/ra_hir/src/expr/validation.rs
+++ /dev/null
@@ -1,137 +0,0 @@
1//! FIXME: write short doc here
2
3use std::sync::Arc;
4
5use hir_def::path::known;
6use hir_expand::diagnostics::DiagnosticSink;
7use ra_syntax::ast;
8use rustc_hash::FxHashSet;
9
10use crate::{
11 db::HirDatabase,
12 diagnostics::{MissingFields, MissingOkInTailExpr},
13 expr::AstPtr,
14 ty::{ApplicationTy, InferenceResult, Ty, TypeCtor},
15 Adt, Function, Name, Path,
16};
17
18use super::{Expr, ExprId, RecordLitField};
19
20pub(crate) struct ExprValidator<'a, 'b: 'a> {
21 func: Function,
22 infer: Arc<InferenceResult>,
23 sink: &'a mut DiagnosticSink<'b>,
24}
25
26impl<'a, 'b> ExprValidator<'a, 'b> {
27 pub(crate) fn new(
28 func: Function,
29 infer: Arc<InferenceResult>,
30 sink: &'a mut DiagnosticSink<'b>,
31 ) -> ExprValidator<'a, 'b> {
32 ExprValidator { func, infer, sink }
33 }
34
35 pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) {
36 let body = self.func.body(db);
37
38 for e in body.exprs() {
39 if let (id, Expr::RecordLit { path, fields, spread }) = e {
40 self.validate_record_literal(id, path, fields, *spread, db);
41 }
42 }
43
44 let body_expr = &body[body.body_expr()];
45 if let Expr::Block { statements: _, tail: Some(t) } = body_expr {
46 self.validate_results_in_tail_expr(body.body_expr(), *t, db);
47 }
48 }
49
50 fn validate_record_literal(
51 &mut self,
52 id: ExprId,
53 _path: &Option<Path>,
54 fields: &[RecordLitField],
55 spread: Option<ExprId>,
56 db: &impl HirDatabase,
57 ) {
58 if spread.is_some() {
59 return;
60 }
61
62 let struct_def = match self.infer[id].as_adt() {
63 Some((Adt::Struct(s), _)) => s,
64 _ => return,
65 };
66
67 let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
68 let missed_fields: Vec<Name> = struct_def
69 .fields(db)
70 .iter()
71 .filter_map(|f| {
72 let name = f.name(db);
73 if lit_fields.contains(&name) {
74 None
75 } else {
76 Some(name)
77 }
78 })
79 .collect();
80 if missed_fields.is_empty() {
81 return;
82 }
83 let source_map = self.func.body_source_map(db);
84
85 if let Some(source_ptr) = source_map.expr_syntax(id) {
86 if let Some(expr) = source_ptr.ast.a() {
87 let root = source_ptr.file_syntax(db);
88 if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
89 if let Some(field_list) = record_lit.record_field_list() {
90 self.sink.push(MissingFields {
91 file: source_ptr.file_id,
92 field_list: AstPtr::new(&field_list),
93 missed_fields,
94 })
95 }
96 }
97 }
98 }
99 }
100
101 fn validate_results_in_tail_expr(
102 &mut self,
103 body_id: ExprId,
104 id: ExprId,
105 db: &impl HirDatabase,
106 ) {
107 // the mismatch will be on the whole block currently
108 let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
109 Some(m) => m,
110 None => return,
111 };
112
113 let std_result_path = known::std_result_result();
114
115 let resolver = self.func.resolver(db);
116 let std_result_enum = match resolver.resolve_known_enum(db, &std_result_path) {
117 Some(it) => it,
118 _ => return,
119 };
120
121 let std_result_ctor = TypeCtor::Adt(Adt::Enum(std_result_enum));
122 let params = match &mismatch.expected {
123 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters,
124 _ => return,
125 };
126
127 if params.len() == 2 && &params[0] == &mismatch.actual {
128 let source_map = self.func.body_source_map(db);
129
130 if let Some(source_ptr) = source_map.expr_syntax(id) {
131 if let Some(expr) = source_ptr.ast.a() {
132 self.sink.push(MissingOkInTailExpr { file: source_ptr.file_id, expr });
133 }
134 }
135 }
136 }
137}
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index 59046edcc..662d3f880 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -19,7 +19,6 @@ use ra_syntax::{
19 SyntaxKind::*, 19 SyntaxKind::*,
20 SyntaxNode, SyntaxNodePtr, TextRange, TextUnit, 20 SyntaxNode, SyntaxNodePtr, TextRange, TextUnit,
21}; 21};
22use rustc_hash::FxHashSet;
23 22
24use crate::{ 23use crate::{
25 db::HirDatabase, 24 db::HirDatabase,
@@ -285,23 +284,15 @@ impl SourceAnalyzer {
285 self.resolve_hir_path(db, &hir_path) 284 self.resolve_hir_path(db, &hir_path)
286 } 285 }
287 286
288 pub fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> { 287 fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> {
289 let mut shadowed = FxHashSet::default();
290 let name = name_ref.as_name(); 288 let name = name_ref.as_name();
291 let source_map = self.body_source_map.as_ref()?; 289 let source_map = self.body_source_map.as_ref()?;
292 let scopes = self.scopes.as_ref()?; 290 let scopes = self.scopes.as_ref()?;
293 let scope = scope_for(scopes, source_map, self.file_id.into(), name_ref.syntax()); 291 let scope = scope_for(scopes, source_map, self.file_id.into(), name_ref.syntax())?;
294 let ret = scopes 292 let entry = scopes.resolve_name_in_scope(scope, &name)?;
295 .scope_chain(scope) 293 Some(ScopeEntryWithSyntax {
296 .flat_map(|scope| scopes.entries(scope).iter()) 294 name: entry.name().clone(),
297 .filter(|entry| shadowed.insert(entry.name())) 295 ptr: source_map.pat_syntax(entry.pat())?.ast,
298 .filter(|entry| entry.name() == &name)
299 .nth(0);
300 ret.and_then(|entry| {
301 Some(ScopeEntryWithSyntax {
302 name: entry.name().clone(),
303 ptr: source_map.pat_syntax(entry.pat())?.ast,
304 })
305 }) 296 })
306 } 297 }
307 298
@@ -309,9 +300,9 @@ impl SourceAnalyzer {
309 self.resolver.process_all_names(db, f) 300 self.resolver.process_all_names(db, f)
310 } 301 }
311 302
303 // FIXME: we only use this in `inline_local_variable` assist, ideally, we
304 // should switch to general reference search infra there.
312 pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> { 305 pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> {
313 // FIXME: at least, this should work with any DefWithBody, but ideally
314 // this should be hir-based altogether
315 let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); 306 let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap();
316 let ptr = Either::A(AstPtr::new(&ast::Pat::from(pat.clone()))); 307 let ptr = Either::A(AstPtr::new(&ast::Pat::from(pat.clone())));
317 fn_def 308 fn_def
@@ -413,11 +404,6 @@ impl SourceAnalyzer {
413 pub(crate) fn inference_result(&self) -> Arc<crate::ty::InferenceResult> { 404 pub(crate) fn inference_result(&self) -> Arc<crate::ty::InferenceResult> {
414 self.infer.clone().unwrap() 405 self.infer.clone().unwrap()
415 } 406 }
416
417 #[cfg(test)]
418 pub(crate) fn scopes(&self) -> Arc<ExprScopes> {
419 self.scopes.clone().unwrap()
420 }
421} 407}
422 408
423fn scope_for( 409fn scope_for(
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs
index 09a39e721..10cb87d37 100644
--- a/crates/ra_hir_def/src/body/scope.rs
+++ b/crates/ra_hir_def/src/body/scope.rs
@@ -67,6 +67,11 @@ impl ExprScopes {
67 std::iter::successors(scope, move |&scope| self.scopes[scope].parent) 67 std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
68 } 68 }
69 69
70 pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> {
71 self.scope_chain(Some(scope))
72 .find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name))
73 }
74
70 pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> { 75 pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
71 self.scope_by_expr.get(&expr).copied() 76 self.scope_by_expr.get(&expr).copied()
72 } 77 }
@@ -163,3 +168,217 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
163 e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)), 168 e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
164 }; 169 };
165} 170}
171
172#[cfg(test)]
173mod tests {
174 use hir_expand::{name::AsName, Source};
175 use ra_db::{fixture::WithFixture, FileId, SourceDatabase};
176 use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
177 use test_utils::{assert_eq_text, extract_offset};
178
179 use crate::{db::DefDatabase2, test_db::TestDB, FunctionId, ModuleDefId};
180
181 fn find_function(db: &TestDB, file_id: FileId) -> FunctionId {
182 let krate = db.test_crate();
183 let crate_def_map = db.crate_def_map(krate);
184
185 let module = crate_def_map.modules_for_file(file_id).next().unwrap();
186 let (_, res) = crate_def_map[module].scope.entries().next().unwrap();
187 match res.def.take_values().unwrap() {
188 ModuleDefId::FunctionId(it) => it,
189 _ => panic!(),
190 }
191 }
192
193 fn do_check(code: &str, expected: &[&str]) {
194 let (off, code) = extract_offset(code);
195 let code = {
196 let mut buf = String::new();
197 let off = u32::from(off) as usize;
198 buf.push_str(&code[..off]);
199 buf.push_str("marker");
200 buf.push_str(&code[off..]);
201 buf
202 };
203
204 let (db, file_id) = TestDB::with_single_file(&code);
205
206 let file_syntax = db.parse(file_id).syntax_node();
207 let marker: ast::PathExpr = find_node_at_offset(&file_syntax, off).unwrap();
208 let function = find_function(&db, file_id);
209
210 let scopes = db.expr_scopes(function.into());
211 let (_body, source_map) = db.body_with_source_map(function.into());
212
213 let expr_id =
214 source_map.node_expr(Source { file_id: file_id.into(), ast: &marker.into() }).unwrap();
215 let scope = scopes.scope_for(expr_id);
216
217 let actual = scopes
218 .scope_chain(scope)
219 .flat_map(|scope| scopes.entries(scope))
220 .map(|it| it.name().to_string())
221 .collect::<Vec<_>>()
222 .join("\n");
223 let expected = expected.join("\n");
224 assert_eq_text!(&expected, &actual);
225 }
226
227 #[test]
228 fn test_lambda_scope() {
229 do_check(
230 r"
231 fn quux(foo: i32) {
232 let f = |bar, baz: i32| {
233 <|>
234 };
235 }",
236 &["bar", "baz", "foo"],
237 );
238 }
239
240 #[test]
241 fn test_call_scope() {
242 do_check(
243 r"
244 fn quux() {
245 f(|x| <|> );
246 }",
247 &["x"],
248 );
249 }
250
251 #[test]
252 fn test_method_call_scope() {
253 do_check(
254 r"
255 fn quux() {
256 z.f(|x| <|> );
257 }",
258 &["x"],
259 );
260 }
261
262 #[test]
263 fn test_loop_scope() {
264 do_check(
265 r"
266 fn quux() {
267 loop {
268 let x = ();
269 <|>
270 };
271 }",
272 &["x"],
273 );
274 }
275
276 #[test]
277 fn test_match() {
278 do_check(
279 r"
280 fn quux() {
281 match () {
282 Some(x) => {
283 <|>
284 }
285 };
286 }",
287 &["x"],
288 );
289 }
290
291 #[test]
292 fn test_shadow_variable() {
293 do_check(
294 r"
295 fn foo(x: String) {
296 let x : &str = &x<|>;
297 }",
298 &["x"],
299 );
300 }
301
302 fn do_check_local_name(code: &str, expected_offset: u32) {
303 let (off, code) = extract_offset(code);
304
305 let (db, file_id) = TestDB::with_single_file(&code);
306
307 let file = db.parse(file_id).ok().unwrap();
308 let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
309 .expect("failed to find a name at the target offset");
310 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
311
312 let function = find_function(&db, file_id);
313
314 let scopes = db.expr_scopes(function.into());
315 let (_body, source_map) = db.body_with_source_map(function.into());
316
317 let expr_scope = {
318 let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
319 let expr_id =
320 source_map.node_expr(Source { file_id: file_id.into(), ast: &expr_ast }).unwrap();
321 scopes.scope_for(expr_id).unwrap()
322 };
323
324 let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
325 let pat_src = source_map.pat_syntax(resolved.pat()).unwrap();
326
327 let local_name = pat_src.ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
328 assert_eq!(local_name.range(), expected_name.syntax().text_range());
329 }
330
331 #[test]
332 fn test_resolve_local_name() {
333 do_check_local_name(
334 r#"
335 fn foo(x: i32, y: u32) {
336 {
337 let z = x * 2;
338 }
339 {
340 let t = x<|> * 3;
341 }
342 }"#,
343 21,
344 );
345 }
346
347 #[test]
348 fn test_resolve_local_name_declaration() {
349 do_check_local_name(
350 r#"
351 fn foo(x: String) {
352 let x : &str = &x<|>;
353 }"#,
354 21,
355 );
356 }
357
358 #[test]
359 fn test_resolve_local_name_shadow() {
360 do_check_local_name(
361 r"
362 fn foo(x: String) {
363 let x : &str = &x;
364 x<|>
365 }
366 ",
367 53,
368 );
369 }
370
371 #[test]
372 fn ref_patterns_contribute_bindings() {
373 do_check_local_name(
374 r"
375 fn foo() {
376 if let Some(&from) = bar() {
377 from<|>;
378 }
379 }
380 ",
381 53,
382 );
383 }
384}
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index 5fc592150..21d5f62e0 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -58,7 +58,7 @@ mod tests;
58 58
59use std::sync::Arc; 59use std::sync::Arc;
60 60
61use hir_expand::{diagnostics::DiagnosticSink, name::Name, MacroDefId}; 61use hir_expand::{ast_id_map::FileAstId, diagnostics::DiagnosticSink, name::Name, MacroDefId};
62use once_cell::sync::Lazy; 62use once_cell::sync::Lazy;
63use ra_arena::Arena; 63use ra_arena::Arena;
64use ra_db::{CrateId, Edition, FileId}; 64use ra_db::{CrateId, Edition, FileId};
@@ -73,7 +73,7 @@ use crate::{
73 diagnostics::DefDiagnostic, path_resolution::ResolveMode, per_ns::PerNs, raw::ImportId, 73 diagnostics::DefDiagnostic, path_resolution::ResolveMode, per_ns::PerNs, raw::ImportId,
74 }, 74 },
75 path::Path, 75 path::Path,
76 AstId, CrateModuleId, ModuleDefId, ModuleId, TraitId, 76 AstId, CrateModuleId, FunctionId, ModuleDefId, ModuleId, TraitId,
77}; 77};
78 78
79/// Contains all top-level defs from a macro-expanded crate 79/// Contains all top-level defs from a macro-expanded crate
@@ -124,6 +124,11 @@ pub struct ModuleData {
124 pub definition: Option<FileId>, 124 pub definition: Option<FileId>,
125} 125}
126 126
127#[derive(Default, Debug, PartialEq, Eq, Clone)]
128pub(crate) struct Declarations {
129 fns: FxHashMap<FileAstId<ast::FnDef>, FunctionId>,
130}
131
127#[derive(Debug, Default, PartialEq, Eq, Clone)] 132#[derive(Debug, Default, PartialEq, Eq, Clone)]
128pub struct ModuleScope { 133pub struct ModuleScope {
129 pub items: FxHashMap<Name, Resolution>, 134 pub items: FxHashMap<Name, Resolution>,
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index 83eef821f..5c899aff3 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -664,7 +664,8 @@ where
664 let name = def.name.clone(); 664 let name = def.name.clone();
665 let def: PerNs = match def.kind { 665 let def: PerNs = match def.kind {
666 raw::DefKind::Function(ast_id) => { 666 raw::DefKind::Function(ast_id) => {
667 PerNs::values(FunctionId::from_ast_id(ctx, ast_id).into()) 667 let f = FunctionId::from_ast_id(ctx, ast_id);
668 PerNs::values(f.into())
668 } 669 }
669 raw::DefKind::Struct(ast_id) => { 670 raw::DefKind::Struct(ast_id) => {
670 let id = StructOrUnionId::from_ast_id(ctx, ast_id).into(); 671 let id = StructOrUnionId::from_ast_id(ctx, ast_id).into();