1 //! Name resolution for expressions.
4 use hir_expand
::name
::Name
;
5 use la_arena
::{Arena, Idx}
;
6 use rustc_hash
::FxHashMap
;
11 expr
::{Expr, ExprId, LabelId, Pat, PatId, Statement}
,
12 BlockId
, DefWithBodyId
,
15 pub type ScopeId
= Idx
<ScopeData
>;
17 #[derive(Debug, PartialEq, Eq)]
18 pub struct ExprScopes
{
19 scopes
: Arena
<ScopeData
>,
20 scope_by_expr
: FxHashMap
<ExprId
, ScopeId
>,
23 #[derive(Debug, PartialEq, Eq)]
24 pub struct ScopeEntry
{
30 pub fn name(&self) -> &Name
{
34 pub fn pat(&self) -> PatId
{
39 #[derive(Debug, PartialEq, Eq)]
40 pub struct ScopeData
{
41 parent
: Option
<ScopeId
>,
42 block
: Option
<BlockId
>,
43 label
: Option
<(LabelId
, Name
)>,
44 entries
: Vec
<ScopeEntry
>,
48 pub(crate) fn expr_scopes_query(db
: &dyn DefDatabase
, def
: DefWithBodyId
) -> Arc
<ExprScopes
> {
49 let body
= db
.body(def
);
50 let mut scopes
= ExprScopes
::new(&body
);
51 scopes
.shrink_to_fit();
55 pub fn entries(&self, scope
: ScopeId
) -> &[ScopeEntry
] {
56 &self.scopes
[scope
].entries
59 /// If `scope` refers to a block expression scope, returns the corresponding `BlockId`.
60 pub fn block(&self, scope
: ScopeId
) -> Option
<BlockId
> {
61 self.scopes
[scope
].block
64 /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
65 pub fn label(&self, scope
: ScopeId
) -> Option
<(LabelId
, Name
)> {
66 self.scopes
[scope
].label
.clone()
69 pub fn scope_chain(&self, scope
: Option
<ScopeId
>) -> impl Iterator
<Item
= ScopeId
> + '_
{
70 std
::iter
::successors(scope
, move |&scope
| self.scopes
[scope
].parent
)
73 pub fn resolve_name_in_scope(&self, scope
: ScopeId
, name
: &Name
) -> Option
<&ScopeEntry
> {
74 self.scope_chain(Some(scope
))
75 .find_map(|scope
| self.entries(scope
).iter().find(|it
| it
.name
== *name
))
78 pub fn scope_for(&self, expr
: ExprId
) -> Option
<ScopeId
> {
79 self.scope_by_expr
.get(&expr
).copied()
82 pub fn scope_by_expr(&self) -> &FxHashMap
<ExprId
, ScopeId
> {
88 fn new(body
: &Body
) -> ExprScopes
{
90 ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() }
;
91 let mut root
= scopes
.root_scope();
92 scopes
.add_params_bindings(body
, root
, &body
.params
);
93 compute_expr_scopes(body
.body_expr
, body
, &mut scopes
, &mut root
);
97 fn root_scope(&mut self) -> ScopeId
{
98 self.scopes
.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] }
)
101 fn new_scope(&mut self, parent
: ScopeId
) -> ScopeId
{
102 self.scopes
.alloc(ScopeData
{
103 parent
: Some(parent
),
110 fn new_labeled_scope(&mut self, parent
: ScopeId
, label
: Option
<(LabelId
, Name
)>) -> ScopeId
{
111 self.scopes
.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] }
)
118 label
: Option
<(LabelId
, Name
)>,
120 self.scopes
.alloc(ScopeData
{
121 parent
: Some(parent
),
128 fn add_bindings(&mut self, body
: &Body
, scope
: ScopeId
, pat
: PatId
) {
129 let pattern
= &body
[pat
];
130 if let Pat
::Bind { name, .. }
= pattern
{
131 let entry
= ScopeEntry { name: name.clone(), pat }
;
132 self.scopes
[scope
].entries
.push(entry
);
135 pattern
.walk_child_pats(|pat
| self.add_bindings(body
, scope
, pat
));
138 fn add_params_bindings(&mut self, body
: &Body
, scope
: ScopeId
, params
: &[PatId
]) {
139 params
.iter().for_each(|pat
| self.add_bindings(body
, scope
, *pat
));
142 fn set_scope(&mut self, node
: ExprId
, scope
: ScopeId
) {
143 self.scope_by_expr
.insert(node
, scope
);
146 fn shrink_to_fit(&mut self) {
147 let ExprScopes { scopes, scope_by_expr }
= self;
148 scopes
.shrink_to_fit();
149 scopes
.values_mut().for_each(|it
| it
.entries
.shrink_to_fit());
150 scope_by_expr
.shrink_to_fit();
154 fn compute_block_scopes(
155 statements
: &[Statement
],
156 tail
: Option
<ExprId
>,
158 scopes
: &mut ExprScopes
,
161 for stmt
in statements
{
163 Statement
::Let { pat, initializer, else_branch, .. }
=> {
164 if let Some(expr
) = initializer
{
165 compute_expr_scopes(*expr
, body
, scopes
, scope
);
167 if let Some(expr
) = else_branch
{
168 compute_expr_scopes(*expr
, body
, scopes
, scope
);
171 *scope
= scopes
.new_scope(*scope
);
172 scopes
.add_bindings(body
, *scope
, *pat
);
174 Statement
::Expr { expr, .. }
=> {
175 compute_expr_scopes(*expr
, body
, scopes
, scope
);
179 if let Some(expr
) = tail
{
180 compute_expr_scopes(expr
, body
, scopes
, scope
);
184 fn compute_expr_scopes(expr
: ExprId
, body
: &Body
, scopes
: &mut ExprScopes
, scope
: &mut ScopeId
) {
186 |label
: &Option
<LabelId
>| label
.map(|label
| (label
, body
.labels
[label
].name
.clone()));
188 scopes
.set_scope(expr
, *scope
);
190 Expr
::Block { statements, tail, id, label }
=> {
191 let mut scope
= scopes
.new_block_scope(*scope
, *id
, make_label(label
));
192 // Overwrite the old scope for the block expr, so that every block scope can be found
193 // via the block itself (important for blocks that only contain items, no expressions).
194 scopes
.set_scope(expr
, scope
);
195 compute_block_scopes(statements
, *tail
, body
, scopes
, &mut scope
);
197 Expr
::For { iterable, pat, body: body_expr, label }
=> {
198 compute_expr_scopes(*iterable
, body
, scopes
, scope
);
199 let mut scope
= scopes
.new_labeled_scope(*scope
, make_label(label
));
200 scopes
.add_bindings(body
, scope
, *pat
);
201 compute_expr_scopes(*body_expr
, body
, scopes
, &mut scope
);
203 Expr
::While { condition, body: body_expr, label }
=> {
204 let mut scope
= scopes
.new_labeled_scope(*scope
, make_label(label
));
205 compute_expr_scopes(*condition
, body
, scopes
, &mut scope
);
206 compute_expr_scopes(*body_expr
, body
, scopes
, &mut scope
);
208 Expr
::Loop { body: body_expr, label }
=> {
209 let mut scope
= scopes
.new_labeled_scope(*scope
, make_label(label
));
210 compute_expr_scopes(*body_expr
, body
, scopes
, &mut scope
);
212 Expr
::Closure { args, body: body_expr, .. }
=> {
213 let mut scope
= scopes
.new_scope(*scope
);
214 scopes
.add_params_bindings(body
, scope
, args
);
215 compute_expr_scopes(*body_expr
, body
, scopes
, &mut scope
);
217 Expr
::Match { expr, arms }
=> {
218 compute_expr_scopes(*expr
, body
, scopes
, scope
);
219 for arm
in arms
.iter() {
220 let mut scope
= scopes
.new_scope(*scope
);
221 scopes
.add_bindings(body
, scope
, arm
.pat
);
222 if let Some(guard
) = arm
.guard
{
223 scope
= scopes
.new_scope(scope
);
224 compute_expr_scopes(guard
, body
, scopes
, &mut scope
);
226 compute_expr_scopes(arm
.expr
, body
, scopes
, &mut scope
);
229 &Expr
::If { condition, then_branch, else_branch }
=> {
230 let mut then_branch_scope
= scopes
.new_scope(*scope
);
231 compute_expr_scopes(condition
, body
, scopes
, &mut then_branch_scope
);
232 compute_expr_scopes(then_branch
, body
, scopes
, &mut then_branch_scope
);
233 if let Some(else_branch
) = else_branch
{
234 compute_expr_scopes(else_branch
, body
, scopes
, scope
);
237 &Expr
::Let { pat, expr }
=> {
238 compute_expr_scopes(expr
, body
, scopes
, scope
);
239 *scope
= scopes
.new_scope(*scope
);
240 scopes
.add_bindings(body
, *scope
, pat
);
242 e
=> e
.walk_child_exprs(|e
| compute_expr_scopes(e
, body
, scopes
, scope
)),
248 use base_db
::{fixture::WithFixture, FileId, SourceDatabase}
;
249 use hir_expand
::{name::AsName, InFile}
;
250 use syntax
::{algo::find_node_at_offset, ast, AstNode}
;
251 use test_utils
::{assert_eq_text, extract_offset}
;
253 use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId}
;
255 fn find_function(db
: &TestDB
, file_id
: FileId
) -> FunctionId
{
256 let krate
= db
.test_crate();
257 let crate_def_map
= db
.crate_def_map(krate
);
259 let module
= crate_def_map
.modules_for_file(file_id
).next().unwrap();
260 let (_
, def
) = crate_def_map
[module
].scope
.entries().next().unwrap();
261 match def
.take_values().unwrap() {
262 ModuleDefId
::FunctionId(it
) => it
,
267 fn do_check(ra_fixture
: &str, expected
: &[&str]) {
268 let (offset
, code
) = extract_offset(ra_fixture
);
270 let mut buf
= String
::new();
271 let off
: usize = offset
.into();
272 buf
.push_str(&code
[..off
]);
273 buf
.push_str("$0marker");
274 buf
.push_str(&code
[off
..]);
278 let (db
, position
) = TestDB
::with_position(&code
);
279 let file_id
= position
.file_id
;
280 let offset
= position
.offset
;
282 let file_syntax
= db
.parse(file_id
).syntax_node();
283 let marker
: ast
::PathExpr
= find_node_at_offset(&file_syntax
, offset
).unwrap();
284 let function
= find_function(&db
, file_id
);
286 let scopes
= db
.expr_scopes(function
.into());
287 let (_body
, source_map
) = db
.body_with_source_map(function
.into());
289 let expr_id
= source_map
290 .node_expr(InFile { file_id: file_id.into(), value: &marker.into() }
)
292 let scope
= scopes
.scope_for(expr_id
);
296 .flat_map(|scope
| scopes
.entries(scope
))
297 .map(|it
| it
.name().to_smol_str())
300 let expected
= expected
.join("\n");
301 assert_eq_text
!(&expected
, &actual
);
305 fn test_lambda_scope() {
309 let f = |bar, baz: i32| {
313 &["bar", "baz", "foo"],
318 fn test_call_scope() {
329 fn test_method_call_scope() {
340 fn test_loop_scope() {
369 fn test_shadow_variable() {
380 fn test_bindings_after_at() {
385 opt @ Some(unit) => {
397 fn macro_inner_item() {
417 fn broken_inner_item() {
429 fn do_check_local_name(ra_fixture
: &str, expected_offset
: u32) {
430 let (db
, position
) = TestDB
::with_position(ra_fixture
);
431 let file_id
= position
.file_id
;
432 let offset
= position
.offset
;
434 let file
= db
.parse(file_id
).ok().unwrap();
435 let expected_name
= find_node_at_offset
::<ast
::Name
>(file
.syntax(), expected_offset
.into())
436 .expect("failed to find a name at the target offset");
437 let name_ref
: ast
::NameRef
= find_node_at_offset(file
.syntax(), offset
).unwrap();
439 let function
= find_function(&db
, file_id
);
441 let scopes
= db
.expr_scopes(function
.into());
442 let (_body
, source_map
) = db
.body_with_source_map(function
.into());
445 let expr_ast
= name_ref
.syntax().ancestors().find_map(ast
::Expr
::cast
).unwrap();
447 source_map
.node_expr(InFile { file_id: file_id.into(), value: &expr_ast }
).unwrap();
448 scopes
.scope_for(expr_id
).unwrap()
451 let resolved
= scopes
.resolve_name_in_scope(expr_scope
, &name_ref
.as_name()).unwrap();
452 let pat_src
= source_map
.pat_syntax(resolved
.pat()).unwrap();
454 let local_name
= pat_src
.value
.either(
455 |it
| it
.syntax_node_ptr().to_node(file
.syntax()),
456 |it
| it
.syntax_node_ptr().to_node(file
.syntax()),
458 assert_eq
!(local_name
.text_range(), expected_name
.syntax().text_range());
462 fn test_resolve_local_name() {
465 fn foo(x: i32, y: u32) {
479 fn test_resolve_local_name_declaration() {
491 fn test_resolve_local_name_shadow() {
504 fn ref_patterns_contribute_bindings() {
508 if let Some(&from) = bar() {
518 fn while_let_adds_binding() {
522 let foo: Option<f32> = None;
523 while let Option::Some(spam) = foo {
533 let foo: Option<f32> = None;
534 while (((let Option::Some(_) = foo))) && let Option::Some(spam) = foo {
544 fn match_guard_if_let() {
548 let foo: Option<f32> = None;
550 _ if let Option::Some(spam) = foo => spam$0,
559 fn let_chains_can_reference_previous_lets() {
563 let foo: Option<i32> = None;
564 if let Some(spam) = foo && spa$0m > 1 && let Some(spam) = foo && spam > 1 {}
572 let foo: Option<i32> = None;
573 if let Some(spam) = foo && spam > 1 && let Some(spam) = foo && sp$0am > 1 {}