//! Name resolution for expressions.
use std::sync::Arc;
-use arena::{Arena, Idx};
use hir_expand::name::Name;
+use la_arena::{Arena, Idx};
use rustc_hash::FxHashMap;
use crate::{
body::Body,
db::DefDatabase,
- expr::{Expr, ExprId, Pat, PatId, Statement},
- DefWithBodyId,
+ expr::{Expr, ExprId, LabelId, MatchGuard, Pat, PatId, Statement},
+ BlockId, DefWithBodyId,
};
pub type ScopeId = Idx<ScopeData>;
#[derive(Debug, PartialEq, Eq)]
pub struct ScopeData {
parent: Option<ScopeId>,
+ block: Option<BlockId>,
+ label: Option<(LabelId, Name)>,
entries: Vec<ScopeEntry>,
}
&self.scopes[scope].entries
}
+ /// If `scope` refers to a block expression scope, returns the corresponding `BlockId`.
+ pub fn block(&self, scope: ScopeId) -> Option<BlockId> {
+ self.scopes[scope].block
+ }
+
+ /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
+ pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
+ self.scopes[scope].label.clone()
+ }
+
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
}
}
fn root_scope(&mut self) -> ScopeId {
- self.scopes.alloc(ScopeData { parent: None, entries: vec![] })
+ self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] })
}
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
- self.scopes.alloc(ScopeData { parent: Some(parent), entries: vec![] })
+ self.scopes.alloc(ScopeData {
+ parent: Some(parent),
+ block: None,
+ label: None,
+ entries: vec![],
+ })
+ }
+
+ fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId {
+ self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] })
+ }
+
+ fn new_block_scope(
+ &mut self,
+ parent: ScopeId,
+ block: BlockId,
+ label: Option<(LabelId, Name)>,
+ ) -> ScopeId {
+ self.scopes.alloc(ScopeData {
+ parent: Some(parent),
+ block: Some(block),
+ label,
+ entries: vec![],
+ })
}
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
) {
for stmt in statements {
match stmt {
- Statement::Let { pat, initializer, .. } => {
+ Statement::Let { pat, initializer, else_branch, .. } => {
if let Some(expr) = initializer {
- scopes.set_scope(*expr, scope);
+ compute_expr_scopes(*expr, body, scopes, scope);
+ }
+ if let Some(expr) = else_branch {
compute_expr_scopes(*expr, body, scopes, scope);
}
scope = scopes.new_scope(scope);
scopes.add_bindings(body, scope, *pat);
}
- Statement::Expr(expr) => {
- scopes.set_scope(*expr, scope);
+ Statement::Expr { expr, .. } => {
compute_expr_scopes(*expr, body, scopes, scope);
}
}
}
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
+ let make_label =
+ |label: &Option<LabelId>| label.map(|label| (label, body.labels[label].name.clone()));
+
scopes.set_scope(expr, scope);
match &body[expr] {
- Expr::Block { statements, tail, .. } => {
- compute_block_scopes(&statements, *tail, body, scopes, scope);
+ Expr::Block { statements, tail, id, label } => {
+ let scope = scopes.new_block_scope(scope, *id, make_label(label));
+ // Overwrite the old scope for the block expr, so that every block scope can be found
+ // via the block itself (important for blocks that only contain items, no expressions).
+ scopes.set_scope(expr, scope);
+ compute_block_scopes(statements, *tail, body, scopes, scope);
}
- Expr::For { iterable, pat, body: body_expr, .. } => {
+ Expr::For { iterable, pat, body: body_expr, label } => {
compute_expr_scopes(*iterable, body, scopes, scope);
- let scope = scopes.new_scope(scope);
+ let scope = scopes.new_labeled_scope(scope, make_label(label));
scopes.add_bindings(body, scope, *pat);
compute_expr_scopes(*body_expr, body, scopes, scope);
}
+ Expr::While { condition, body: body_expr, label } => {
+ let scope = scopes.new_labeled_scope(scope, make_label(label));
+ compute_expr_scopes(*condition, body, scopes, scope);
+ compute_expr_scopes(*body_expr, body, scopes, scope);
+ }
+ Expr::Loop { body: body_expr, label } => {
+ let scope = scopes.new_labeled_scope(scope, make_label(label));
+ compute_expr_scopes(*body_expr, body, scopes, scope);
+ }
Expr::Lambda { args, body: body_expr, .. } => {
let scope = scopes.new_scope(scope);
- scopes.add_params_bindings(body, scope, &args);
+ scopes.add_params_bindings(body, scope, args);
compute_expr_scopes(*body_expr, body, scopes, scope);
}
Expr::Match { expr, arms } => {
compute_expr_scopes(*expr, body, scopes, scope);
- for arm in arms {
- let scope = scopes.new_scope(scope);
+ for arm in arms.iter() {
+ let mut scope = scopes.new_scope(scope);
scopes.add_bindings(body, scope, arm.pat);
- if let Some(guard) = arm.guard {
- scopes.set_scope(guard, scope);
- compute_expr_scopes(guard, body, scopes, scope);
- }
+ match arm.guard {
+ Some(MatchGuard::If { expr: guard }) => {
+ scopes.set_scope(guard, scope);
+ compute_expr_scopes(guard, body, scopes, scope);
+ }
+ Some(MatchGuard::IfLet { pat, expr: guard }) => {
+ scopes.set_scope(guard, scope);
+ compute_expr_scopes(guard, body, scopes, scope);
+ scope = scopes.new_scope(scope);
+ scopes.add_bindings(body, scope, pat);
+ }
+ _ => {}
+ };
scopes.set_scope(arm.expr, scope);
compute_expr_scopes(arm.expr, body, scopes, scope);
}
use base_db::{fixture::WithFixture, FileId, SourceDatabase};
use hir_expand::{name::AsName, InFile};
use syntax::{algo::find_node_at_offset, ast, AstNode};
- use test_utils::{assert_eq_text, extract_offset, mark};
+ use test_utils::{assert_eq_text, extract_offset};
use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId};
let mut buf = String::new();
let off: usize = offset.into();
buf.push_str(&code[..off]);
- buf.push_str("<|>marker");
+ buf.push_str("$0marker");
buf.push_str(&code[off..]);
buf
};
let actual = scopes
.scope_chain(scope)
.flat_map(|scope| scopes.entries(scope))
- .map(|it| it.name().to_string())
+ .map(|it| it.name().to_smol_str())
.collect::<Vec<_>>()
.join("\n");
let expected = expected.join("\n");
r"
fn quux(foo: i32) {
let f = |bar, baz: i32| {
- <|>
+ $0
};
}",
&["bar", "baz", "foo"],
do_check(
r"
fn quux() {
- f(|x| <|> );
+ f(|x| $0 );
}",
&["x"],
);
do_check(
r"
fn quux() {
- z.f(|x| <|> );
+ z.f(|x| $0 );
}",
&["x"],
);
fn quux() {
loop {
let x = ();
- <|>
+ $0
};
}",
&["x"],
fn quux() {
match () {
Some(x) => {
- <|>
+ $0
}
};
}",
do_check(
r"
fn foo(x: String) {
- let x : &str = &x<|>;
+ let x : &str = &x$0;
}",
&["x"],
);
fn foo() {
match Some(()) {
opt @ Some(unit) => {
- <|>
+ $0
}
_ => {}
}
fn foo() {
mac!();
- <|>
+ $0
}
",
&[],
r"
fn foo() {
trait {}
- <|>
+ $0
}
",
&[],
let z = x * 2;
}
{
- let t = x<|> * 3;
+ let t = x$0 * 3;
}
}
"#,
do_check_local_name(
r#"
fn foo(x: String) {
- let x : &str = &x<|>;
+ let x : &str = &x$0;
}
"#,
7,
r"
fn foo(x: String) {
let x : &str = &x;
- x<|>
+ x$0
}
",
28,
r"
fn foo() {
if let Some(&from) = bar() {
- from<|>;
+ from$0;
}
}
",
#[test]
fn while_let_desugaring() {
- mark::check!(infer_resolve_while_let);
+ cov_mark::check!(infer_resolve_while_let);
do_check_local_name(
r#"
fn test() {
let foo: Option<f32> = None;
while let Option::Some(spam) = foo {
- spam<|>
+ spam$0
}
}
"#,