mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 21:43:37 +00:00
Merge #2240
2240: Move scopes to hir_def r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
5c3ccc5508
7 changed files with 360 additions and 368 deletions
|
@ -85,7 +85,7 @@ pub trait DefDatabase: HirDebugDatabase + DefDatabase2 {
|
||||||
#[salsa::query_group(HirDatabaseStorage)]
|
#[salsa::query_group(HirDatabaseStorage)]
|
||||||
#[salsa::requires(salsa::Database)]
|
#[salsa::requires(salsa::Database)]
|
||||||
pub trait HirDatabase: DefDatabase + AstDatabase {
|
pub trait HirDatabase: DefDatabase + AstDatabase {
|
||||||
#[salsa::invoke(ExprScopes::expr_scopes_query)]
|
#[salsa::invoke(crate::expr::expr_scopes_query)]
|
||||||
fn expr_scopes(&self, def: DefWithBody) -> Arc<ExprScopes>;
|
fn expr_scopes(&self, def: DefWithBody) -> Arc<ExprScopes>;
|
||||||
|
|
||||||
#[salsa::invoke(crate::ty::infer_query)]
|
#[salsa::invoke(crate::ty::infer_query)]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//! FIXME: write short doc here
|
//! FIXME: write short doc here
|
||||||
|
|
||||||
pub(crate) mod scope;
|
|
||||||
pub(crate) mod validation;
|
pub(crate) mod validation;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -9,10 +8,11 @@ use ra_syntax::{ast, AstPtr};
|
||||||
|
|
||||||
use crate::{db::HirDatabase, DefWithBody, HasSource, Resolver};
|
use crate::{db::HirDatabase, DefWithBody, HasSource, Resolver};
|
||||||
|
|
||||||
pub use self::scope::ExprScopes;
|
|
||||||
|
|
||||||
pub use hir_def::{
|
pub use hir_def::{
|
||||||
body::{Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource},
|
body::{
|
||||||
|
scope::{ExprScopes, ScopeEntry, ScopeId},
|
||||||
|
Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource,
|
||||||
|
},
|
||||||
expr::{
|
expr::{
|
||||||
ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
|
ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
|
||||||
MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
|
MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
|
||||||
|
@ -49,6 +49,11 @@ pub(crate) fn body_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<Body> {
|
||||||
db.body_with_source_map(def).0
|
db.body_with_source_map(def).0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> {
|
||||||
|
let body = db.body(def);
|
||||||
|
Arc::new(ExprScopes::new(&*body))
|
||||||
|
}
|
||||||
|
|
||||||
// needs arbitrary_self_types to be a method... or maybe move to the def?
|
// needs arbitrary_self_types to be a method... or maybe move to the def?
|
||||||
pub(crate) fn resolver_for_expr(
|
pub(crate) fn resolver_for_expr(
|
||||||
db: &impl HirDatabase,
|
db: &impl HirDatabase,
|
||||||
|
@ -62,7 +67,7 @@ pub(crate) fn resolver_for_expr(
|
||||||
pub(crate) fn resolver_for_scope(
|
pub(crate) fn resolver_for_scope(
|
||||||
db: &impl HirDatabase,
|
db: &impl HirDatabase,
|
||||||
owner: DefWithBody,
|
owner: DefWithBody,
|
||||||
scope_id: Option<scope::ScopeId>,
|
scope_id: Option<ScopeId>,
|
||||||
) -> Resolver {
|
) -> Resolver {
|
||||||
let mut r = owner.resolver(db);
|
let mut r = owner.resolver(db);
|
||||||
let scopes = db.expr_scopes(owner);
|
let scopes = db.expr_scopes(owner);
|
||||||
|
@ -72,3 +77,192 @@ pub(crate) fn resolver_for_scope(
|
||||||
}
|
}
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use hir_expand::Source;
|
||||||
|
use ra_db::{fixture::WithFixture, SourceDatabase};
|
||||||
|
use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
|
||||||
|
use test_utils::{assert_eq_text, extract_offset};
|
||||||
|
|
||||||
|
use crate::{source_binder::SourceAnalyzer, test_db::TestDB};
|
||||||
|
|
||||||
|
fn do_check(code: &str, expected: &[&str]) {
|
||||||
|
let (off, code) = extract_offset(code);
|
||||||
|
let code = {
|
||||||
|
let mut buf = String::new();
|
||||||
|
let off = u32::from(off) as usize;
|
||||||
|
buf.push_str(&code[..off]);
|
||||||
|
buf.push_str("marker");
|
||||||
|
buf.push_str(&code[off..]);
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
|
||||||
|
let (db, file_id) = TestDB::with_single_file(&code);
|
||||||
|
|
||||||
|
let file = db.parse(file_id).ok().unwrap();
|
||||||
|
let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
|
||||||
|
let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None);
|
||||||
|
|
||||||
|
let scopes = analyzer.scopes();
|
||||||
|
let expr_id = analyzer
|
||||||
|
.body_source_map()
|
||||||
|
.node_expr(Source { file_id: file_id.into(), ast: &marker.into() })
|
||||||
|
.unwrap();
|
||||||
|
let scope = scopes.scope_for(expr_id);
|
||||||
|
|
||||||
|
let actual = scopes
|
||||||
|
.scope_chain(scope)
|
||||||
|
.flat_map(|scope| scopes.entries(scope))
|
||||||
|
.map(|it| it.name().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let expected = expected.join("\n");
|
||||||
|
assert_eq_text!(&expected, &actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lambda_scope() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn quux(foo: i32) {
|
||||||
|
let f = |bar, baz: i32| {
|
||||||
|
<|>
|
||||||
|
};
|
||||||
|
}",
|
||||||
|
&["bar", "baz", "foo"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_scope() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
f(|x| <|> );
|
||||||
|
}",
|
||||||
|
&["x"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_method_call_scope() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
z.f(|x| <|> );
|
||||||
|
}",
|
||||||
|
&["x"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_loop_scope() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
loop {
|
||||||
|
let x = ();
|
||||||
|
<|>
|
||||||
|
};
|
||||||
|
}",
|
||||||
|
&["x"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_match() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
match () {
|
||||||
|
Some(x) => {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}",
|
||||||
|
&["x"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shadow_variable() {
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
fn foo(x: String) {
|
||||||
|
let x : &str = &x<|>;
|
||||||
|
}",
|
||||||
|
&["x"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_check_local_name(code: &str, expected_offset: u32) {
|
||||||
|
let (off, code) = extract_offset(code);
|
||||||
|
|
||||||
|
let (db, file_id) = TestDB::with_single_file(&code);
|
||||||
|
let file = db.parse(file_id).ok().unwrap();
|
||||||
|
let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
|
||||||
|
.expect("failed to find a name at the target offset");
|
||||||
|
let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
|
||||||
|
let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None);
|
||||||
|
|
||||||
|
let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap();
|
||||||
|
let local_name =
|
||||||
|
local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
|
||||||
|
assert_eq!(local_name.range(), expected_name.syntax().text_range());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_local_name() {
|
||||||
|
do_check_local_name(
|
||||||
|
r#"
|
||||||
|
fn foo(x: i32, y: u32) {
|
||||||
|
{
|
||||||
|
let z = x * 2;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let t = x<|> * 3;
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
21,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_local_name_declaration() {
|
||||||
|
do_check_local_name(
|
||||||
|
r#"
|
||||||
|
fn foo(x: String) {
|
||||||
|
let x : &str = &x<|>;
|
||||||
|
}"#,
|
||||||
|
21,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_local_name_shadow() {
|
||||||
|
do_check_local_name(
|
||||||
|
r"
|
||||||
|
fn foo(x: String) {
|
||||||
|
let x : &str = &x;
|
||||||
|
x<|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
53,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ref_patterns_contribute_bindings() {
|
||||||
|
do_check_local_name(
|
||||||
|
r"
|
||||||
|
fn foo() {
|
||||||
|
if let Some(&from) = bar() {
|
||||||
|
from<|>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
53,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,353 +0,0 @@
|
||||||
//! FIXME: write short doc here
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use ra_arena::{impl_arena_id, Arena, RawId};
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
db::HirDatabase,
|
|
||||||
expr::{Body, Expr, ExprId, Pat, PatId, Statement},
|
|
||||||
DefWithBody, Name,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ScopeId(RawId);
|
|
||||||
impl_arena_id!(ScopeId);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct ExprScopes {
|
|
||||||
scopes: Arena<ScopeId, ScopeData>,
|
|
||||||
scope_by_expr: FxHashMap<ExprId, ScopeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) struct ScopeEntry {
|
|
||||||
name: Name,
|
|
||||||
pat: PatId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScopeEntry {
|
|
||||||
pub(crate) fn name(&self) -> &Name {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn pat(&self) -> PatId {
|
|
||||||
self.pat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) struct ScopeData {
|
|
||||||
parent: Option<ScopeId>,
|
|
||||||
entries: Vec<ScopeEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExprScopes {
|
|
||||||
pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> {
|
|
||||||
let body = db.body(def);
|
|
||||||
let res = ExprScopes::new(&*body);
|
|
||||||
Arc::new(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(body: &Body) -> ExprScopes {
|
|
||||||
let mut scopes =
|
|
||||||
ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
|
|
||||||
let root = scopes.root_scope();
|
|
||||||
scopes.add_params_bindings(body, root, body.params());
|
|
||||||
compute_expr_scopes(body.body_expr(), body, &mut scopes, root);
|
|
||||||
scopes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
|
||||||
&self.scopes[scope].entries
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
|
|
||||||
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
|
|
||||||
self.scope_by_expr.get(&expr).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
|
|
||||||
&self.scope_by_expr
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root_scope(&mut self) -> ScopeId {
|
|
||||||
self.scopes.alloc(ScopeData { parent: None, entries: vec![] })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
|
||||||
self.scopes.alloc(ScopeData { parent: Some(parent), entries: vec![] })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
|
|
||||||
match &body[pat] {
|
|
||||||
Pat::Bind { name, .. } => {
|
|
||||||
// bind can have a sub pattern, but it's actually not allowed
|
|
||||||
// to bind to things in there
|
|
||||||
let entry = ScopeEntry { name: name.clone(), pat };
|
|
||||||
self.scopes[scope].entries.push(entry)
|
|
||||||
}
|
|
||||||
p => p.walk_child_pats(|pat| self.add_bindings(body, scope, pat)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
|
|
||||||
params.iter().for_each(|pat| self.add_bindings(body, scope, *pat));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
|
|
||||||
self.scope_by_expr.insert(node, scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_block_scopes(
|
|
||||||
statements: &[Statement],
|
|
||||||
tail: Option<ExprId>,
|
|
||||||
body: &Body,
|
|
||||||
scopes: &mut ExprScopes,
|
|
||||||
mut scope: ScopeId,
|
|
||||||
) {
|
|
||||||
for stmt in statements {
|
|
||||||
match stmt {
|
|
||||||
Statement::Let { pat, initializer, .. } => {
|
|
||||||
if let Some(expr) = initializer {
|
|
||||||
scopes.set_scope(*expr, scope);
|
|
||||||
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);
|
|
||||||
compute_expr_scopes(*expr, body, scopes, scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(expr) = tail {
|
|
||||||
compute_expr_scopes(expr, body, scopes, scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
|
|
||||||
scopes.set_scope(expr, scope);
|
|
||||||
match &body[expr] {
|
|
||||||
Expr::Block { statements, tail } => {
|
|
||||||
compute_block_scopes(&statements, *tail, body, scopes, scope);
|
|
||||||
}
|
|
||||||
Expr::For { iterable, pat, body: body_expr } => {
|
|
||||||
compute_expr_scopes(*iterable, body, scopes, scope);
|
|
||||||
let scope = scopes.new_scope(scope);
|
|
||||||
scopes.add_bindings(body, scope, *pat);
|
|
||||||
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);
|
|
||||||
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 pat in &arm.pats {
|
|
||||||
scopes.add_bindings(body, scope, *pat);
|
|
||||||
}
|
|
||||||
scopes.set_scope(arm.expr, scope);
|
|
||||||
compute_expr_scopes(arm.expr, body, scopes, scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use hir_expand::Source;
|
|
||||||
use ra_db::{fixture::WithFixture, SourceDatabase};
|
|
||||||
use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
|
|
||||||
use test_utils::{assert_eq_text, extract_offset};
|
|
||||||
|
|
||||||
use crate::{source_binder::SourceAnalyzer, test_db::TestDB};
|
|
||||||
|
|
||||||
fn do_check(code: &str, expected: &[&str]) {
|
|
||||||
let (off, code) = extract_offset(code);
|
|
||||||
let code = {
|
|
||||||
let mut buf = String::new();
|
|
||||||
let off = u32::from(off) as usize;
|
|
||||||
buf.push_str(&code[..off]);
|
|
||||||
buf.push_str("marker");
|
|
||||||
buf.push_str(&code[off..]);
|
|
||||||
buf
|
|
||||||
};
|
|
||||||
|
|
||||||
let (db, file_id) = TestDB::with_single_file(&code);
|
|
||||||
let file = db.parse(file_id).ok().unwrap();
|
|
||||||
let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
|
|
||||||
let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None);
|
|
||||||
|
|
||||||
let scopes = analyzer.scopes();
|
|
||||||
let expr_id = analyzer
|
|
||||||
.body_source_map()
|
|
||||||
.node_expr(Source { file_id: file_id.into(), ast: &marker.into() })
|
|
||||||
.unwrap();
|
|
||||||
let scope = scopes.scope_for(expr_id);
|
|
||||||
|
|
||||||
let actual = scopes
|
|
||||||
.scope_chain(scope)
|
|
||||||
.flat_map(|scope| scopes.entries(scope))
|
|
||||||
.map(|it| it.name().to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
let expected = expected.join("\n");
|
|
||||||
assert_eq_text!(&expected, &actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lambda_scope() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn quux(foo: i32) {
|
|
||||||
let f = |bar, baz: i32| {
|
|
||||||
<|>
|
|
||||||
};
|
|
||||||
}",
|
|
||||||
&["bar", "baz", "foo"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_call_scope() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
f(|x| <|> );
|
|
||||||
}",
|
|
||||||
&["x"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_method_call_scope() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
z.f(|x| <|> );
|
|
||||||
}",
|
|
||||||
&["x"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_loop_scope() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
loop {
|
|
||||||
let x = ();
|
|
||||||
<|>
|
|
||||||
};
|
|
||||||
}",
|
|
||||||
&["x"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_match() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
match () {
|
|
||||||
Some(x) => {
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}",
|
|
||||||
&["x"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shadow_variable() {
|
|
||||||
do_check(
|
|
||||||
r"
|
|
||||||
fn foo(x: String) {
|
|
||||||
let x : &str = &x<|>;
|
|
||||||
}",
|
|
||||||
&["x"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_check_local_name(code: &str, expected_offset: u32) {
|
|
||||||
let (off, code) = extract_offset(code);
|
|
||||||
|
|
||||||
let (db, file_id) = TestDB::with_single_file(&code);
|
|
||||||
let file = db.parse(file_id).ok().unwrap();
|
|
||||||
let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
|
|
||||||
.expect("failed to find a name at the target offset");
|
|
||||||
let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
|
|
||||||
let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None);
|
|
||||||
|
|
||||||
let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap();
|
|
||||||
let local_name =
|
|
||||||
local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
|
|
||||||
assert_eq!(local_name.range(), expected_name.syntax().text_range());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resolve_local_name() {
|
|
||||||
do_check_local_name(
|
|
||||||
r#"
|
|
||||||
fn foo(x: i32, y: u32) {
|
|
||||||
{
|
|
||||||
let z = x * 2;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let t = x<|> * 3;
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
21,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resolve_local_name_declaration() {
|
|
||||||
do_check_local_name(
|
|
||||||
r#"
|
|
||||||
fn foo(x: String) {
|
|
||||||
let x : &str = &x<|>;
|
|
||||||
}"#,
|
|
||||||
21,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resolve_local_name_shadow() {
|
|
||||||
do_check_local_name(
|
|
||||||
r"
|
|
||||||
fn foo(x: String) {
|
|
||||||
let x : &str = &x;
|
|
||||||
x<|>
|
|
||||||
}
|
|
||||||
",
|
|
||||||
53,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ref_patterns_contribute_bindings() {
|
|
||||||
do_check_local_name(
|
|
||||||
r"
|
|
||||||
fn foo() {
|
|
||||||
if let Some(&from) = bar() {
|
|
||||||
from<|>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
53,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,10 +13,7 @@ use rustc_hash::FxHashSet;
|
||||||
use crate::{
|
use crate::{
|
||||||
code_model::Crate,
|
code_model::Crate,
|
||||||
db::{DefDatabase, HirDatabase},
|
db::{DefDatabase, HirDatabase},
|
||||||
expr::{
|
expr::{ExprScopes, PatId, ScopeId},
|
||||||
scope::{ExprScopes, ScopeId},
|
|
||||||
PatId,
|
|
||||||
},
|
|
||||||
generics::GenericParams,
|
generics::GenericParams,
|
||||||
impl_block::ImplBlock,
|
impl_block::ImplBlock,
|
||||||
Adt, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, PerNs, Static, Struct, Trait,
|
Adt, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, PerNs, Static, Struct, Trait,
|
||||||
|
|
|
@ -23,11 +23,7 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::HirDatabase,
|
db::HirDatabase,
|
||||||
expr::{
|
expr::{self, BodySourceMap, ExprScopes, ScopeId},
|
||||||
self,
|
|
||||||
scope::{ExprScopes, ScopeId},
|
|
||||||
BodySourceMap,
|
|
||||||
},
|
|
||||||
ids::LocationCtx,
|
ids::LocationCtx,
|
||||||
resolve::{ScopeDef, TypeNs, ValueNs},
|
resolve::{ScopeDef, TypeNs, ValueNs},
|
||||||
ty::method_resolution::{self, implements_trait},
|
ty::method_resolution::{self, implements_trait},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! FIXME: write short doc here
|
//! FIXME: write short doc here
|
||||||
mod lower;
|
mod lower;
|
||||||
|
pub mod scope;
|
||||||
|
|
||||||
use std::{ops::Index, sync::Arc};
|
use std::{ops::Index, sync::Arc};
|
||||||
|
|
||||||
|
|
157
crates/ra_hir_def/src/body/scope.rs
Normal file
157
crates/ra_hir_def/src/body/scope.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
//! FIXME: write short doc here
|
||||||
|
|
||||||
|
use hir_expand::name::Name;
|
||||||
|
use ra_arena::{impl_arena_id, Arena, RawId};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
body::Body,
|
||||||
|
expr::{Expr, ExprId, Pat, PatId, Statement},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ScopeId(RawId);
|
||||||
|
impl_arena_id!(ScopeId);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExprScopes {
|
||||||
|
scopes: Arena<ScopeId, ScopeData>,
|
||||||
|
scope_by_expr: FxHashMap<ExprId, ScopeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ScopeEntry {
|
||||||
|
name: Name,
|
||||||
|
pat: PatId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeEntry {
|
||||||
|
pub fn name(&self) -> &Name {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pat(&self) -> PatId {
|
||||||
|
self.pat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ScopeData {
|
||||||
|
parent: Option<ScopeId>,
|
||||||
|
entries: Vec<ScopeEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExprScopes {
|
||||||
|
pub fn new(body: &Body) -> ExprScopes {
|
||||||
|
let mut scopes =
|
||||||
|
ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
|
||||||
|
let root = scopes.root_scope();
|
||||||
|
scopes.add_params_bindings(body, root, body.params());
|
||||||
|
compute_expr_scopes(body.body_expr(), body, &mut scopes, root);
|
||||||
|
scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
||||||
|
&self.scopes[scope].entries
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
|
||||||
|
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
|
||||||
|
self.scope_by_expr.get(&expr).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
|
||||||
|
&self.scope_by_expr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_scope(&mut self) -> ScopeId {
|
||||||
|
self.scopes.alloc(ScopeData { parent: None, entries: vec![] })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||||
|
self.scopes.alloc(ScopeData { parent: Some(parent), entries: vec![] })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
|
||||||
|
match &body[pat] {
|
||||||
|
Pat::Bind { name, .. } => {
|
||||||
|
// bind can have a sub pattern, but it's actually not allowed
|
||||||
|
// to bind to things in there
|
||||||
|
let entry = ScopeEntry { name: name.clone(), pat };
|
||||||
|
self.scopes[scope].entries.push(entry)
|
||||||
|
}
|
||||||
|
p => p.walk_child_pats(|pat| self.add_bindings(body, scope, pat)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
|
||||||
|
params.iter().for_each(|pat| self.add_bindings(body, scope, *pat));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
|
||||||
|
self.scope_by_expr.insert(node, scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_block_scopes(
|
||||||
|
statements: &[Statement],
|
||||||
|
tail: Option<ExprId>,
|
||||||
|
body: &Body,
|
||||||
|
scopes: &mut ExprScopes,
|
||||||
|
mut scope: ScopeId,
|
||||||
|
) {
|
||||||
|
for stmt in statements {
|
||||||
|
match stmt {
|
||||||
|
Statement::Let { pat, initializer, .. } => {
|
||||||
|
if let Some(expr) = initializer {
|
||||||
|
scopes.set_scope(*expr, scope);
|
||||||
|
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);
|
||||||
|
compute_expr_scopes(*expr, body, scopes, scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(expr) = tail {
|
||||||
|
compute_expr_scopes(expr, body, scopes, scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
|
||||||
|
scopes.set_scope(expr, scope);
|
||||||
|
match &body[expr] {
|
||||||
|
Expr::Block { statements, tail } => {
|
||||||
|
compute_block_scopes(&statements, *tail, body, scopes, scope);
|
||||||
|
}
|
||||||
|
Expr::For { iterable, pat, body: body_expr } => {
|
||||||
|
compute_expr_scopes(*iterable, body, scopes, scope);
|
||||||
|
let scope = scopes.new_scope(scope);
|
||||||
|
scopes.add_bindings(body, scope, *pat);
|
||||||
|
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);
|
||||||
|
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 pat in &arm.pats {
|
||||||
|
scopes.add_bindings(body, scope, *pat);
|
||||||
|
}
|
||||||
|
scopes.set_scope(arm.expr, scope);
|
||||||
|
compute_expr_scopes(arm.expr, body, scopes, scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue