mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
introduce SourceAnalyzer
This commit is contained in:
parent
e6e2571bdf
commit
10d66d63d7
15 changed files with 205 additions and 186 deletions
|
@ -4,17 +4,10 @@ use crate::completion::{CompletionContext, Completions};
|
|||
|
||||
/// Complete dot accesses, i.e. fields or methods (currently only fields).
|
||||
pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let (function, receiver) = match (&ctx.function, ctx.dot_receiver) {
|
||||
(Some(function), Some(receiver)) => (function, receiver),
|
||||
_ => return,
|
||||
};
|
||||
let infer_result = function.infer(ctx.db);
|
||||
let source_map = function.body_source_map(ctx.db);
|
||||
let expr = match source_map.node_expr(receiver) {
|
||||
Some(expr) => expr,
|
||||
let receiver_ty = match ctx.dot_receiver.and_then(|it| ctx.analyzer.type_of(ctx.db, it)) {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
let receiver_ty = infer_result[expr].clone();
|
||||
if !ctx.is_call {
|
||||
complete_fields(acc, ctx, receiver_ty.clone());
|
||||
}
|
||||
|
|
|
@ -4,17 +4,10 @@ use crate::completion::{CompletionContext, Completions};
|
|||
|
||||
/// Complete fields in fields literals.
|
||||
pub(super) fn complete_struct_literal(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let (function, struct_lit) = match (&ctx.function, ctx.struct_lit_syntax) {
|
||||
(Some(function), Some(struct_lit)) => (function, struct_lit),
|
||||
_ => return,
|
||||
};
|
||||
let infer_result = function.infer(ctx.db);
|
||||
let source_map = function.body_source_map(ctx.db);
|
||||
let expr = match source_map.node_expr(struct_lit.into()) {
|
||||
Some(expr) => expr,
|
||||
let ty = match ctx.struct_lit_syntax.and_then(|it| ctx.analyzer.type_of(ctx.db, it.into())) {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
let ty = infer_result[expr].clone();
|
||||
let (adt, substs) = match ty.as_adt() {
|
||||
Some(res) => res,
|
||||
_ => return,
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::{db, FilePosition};
|
|||
#[derive(Debug)]
|
||||
pub(crate) struct CompletionContext<'a> {
|
||||
pub(super) db: &'a db::RootDatabase,
|
||||
pub(super) analyzer: hir::SourceAnalyser,
|
||||
pub(super) offset: TextUnit,
|
||||
pub(super) token: SyntaxToken<'a>,
|
||||
pub(super) resolver: Resolver,
|
||||
|
@ -50,8 +51,10 @@ impl<'a> CompletionContext<'a> {
|
|||
let resolver = source_binder::resolver_for_position(db, position);
|
||||
let module = source_binder::module_from_position(db, position);
|
||||
let token = find_token_at_offset(original_file.syntax(), position.offset).left_biased()?;
|
||||
let analyzer = hir::SourceAnalyser::new(db, position.file_id, token.parent());
|
||||
let mut ctx = CompletionContext {
|
||||
db,
|
||||
analyzer,
|
||||
token,
|
||||
offset: position.offset,
|
||||
resolver,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use ra_db::{FileId, SourceDatabase};
|
||||
use ra_syntax::{
|
||||
SyntaxNode, SyntaxNodePtr, AstNode, SmolStr, TextRange, TreeArc,
|
||||
SyntaxNode, AstNode, SmolStr, TextRange, TreeArc, AstPtr,
|
||||
SyntaxKind::{self, NAME},
|
||||
ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner},
|
||||
algo::visit::{visitor, Visitor},
|
||||
};
|
||||
use hir::{ModuleSource, FieldSource, Name, ImplItem};
|
||||
use hir::{ModuleSource, FieldSource, ImplItem, Either};
|
||||
|
||||
use crate::{FileSymbol, db::RootDatabase};
|
||||
|
||||
|
@ -74,15 +74,25 @@ impl NavigationTarget {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_scope_entry(
|
||||
pub(crate) fn from_pat(
|
||||
db: &RootDatabase,
|
||||
file_id: FileId,
|
||||
name: Name,
|
||||
ptr: SyntaxNodePtr,
|
||||
pat: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>,
|
||||
) -> NavigationTarget {
|
||||
let file = db.parse(file_id);
|
||||
let (name, full_range) = match pat {
|
||||
Either::A(pat) => match pat.to_node(&file).kind() {
|
||||
ast::PatKind::BindPat(pat) => {
|
||||
return NavigationTarget::from_bind_pat(file_id, &pat)
|
||||
}
|
||||
_ => ("_".into(), pat.syntax_node_ptr().range()),
|
||||
},
|
||||
Either::B(slf) => ("self".into(), slf.syntax_node_ptr().range()),
|
||||
};
|
||||
NavigationTarget {
|
||||
file_id,
|
||||
name: name.to_string().into(),
|
||||
full_range: ptr.range(),
|
||||
name,
|
||||
full_range,
|
||||
focus_range: None,
|
||||
kind: NAME,
|
||||
container_name: None,
|
||||
|
@ -229,6 +239,7 @@ impl NavigationTarget {
|
|||
|
||||
/// Allows `NavigationTarget` to be created from a `NameOwner`
|
||||
pub(crate) fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget {
|
||||
//FIXME: use `_` instead of empty string
|
||||
let name = node.name().map(|it| it.text().clone()).unwrap_or_default();
|
||||
let focus_range = node.name().map(|it| it.syntax().range());
|
||||
NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax())
|
||||
|
|
|
@ -5,7 +5,6 @@ use ra_syntax::{
|
|||
SyntaxNode,
|
||||
};
|
||||
use test_utils::tested_by;
|
||||
use hir::Resolution;
|
||||
|
||||
use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
|
||||
|
||||
|
@ -48,127 +47,72 @@ pub(crate) fn reference_definition(
|
|||
) -> ReferenceResult {
|
||||
use self::ReferenceResult::*;
|
||||
|
||||
let function = hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax());
|
||||
let analyzer = hir::SourceAnalyser::new(db, file_id, name_ref.syntax());
|
||||
|
||||
if let Some(function) = function {
|
||||
// Check if it is a method
|
||||
if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) {
|
||||
tested_by!(goto_definition_works_for_methods);
|
||||
let infer_result = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
let expr = ast::Expr::cast(method_call.syntax()).unwrap();
|
||||
if let Some(func) =
|
||||
source_map.node_expr(expr).and_then(|it| infer_result.method_resolution(it))
|
||||
{
|
||||
return Exact(NavigationTarget::from_function(db, func));
|
||||
};
|
||||
}
|
||||
// It could also be a field access
|
||||
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
|
||||
tested_by!(goto_definition_works_for_fields);
|
||||
let infer_result = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
let expr = ast::Expr::cast(field_expr.syntax()).unwrap();
|
||||
if let Some(field) =
|
||||
source_map.node_expr(expr).and_then(|it| infer_result.field_resolution(it))
|
||||
{
|
||||
return Exact(NavigationTarget::from_field(db, field));
|
||||
};
|
||||
// Special cases:
|
||||
|
||||
// Check if it is a method
|
||||
if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) {
|
||||
tested_by!(goto_definition_works_for_methods);
|
||||
if let Some(func) = analyzer.resolve_method_call(method_call) {
|
||||
return Exact(NavigationTarget::from_function(db, func));
|
||||
}
|
||||
}
|
||||
// It could also be a field access
|
||||
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
|
||||
tested_by!(goto_definition_works_for_fields);
|
||||
if let Some(field) = analyzer.resolve_field(field_expr) {
|
||||
return Exact(NavigationTarget::from_field(db, field));
|
||||
};
|
||||
}
|
||||
|
||||
// It could also be a named field
|
||||
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) {
|
||||
tested_by!(goto_definition_works_for_named_fields);
|
||||
// It could also be a named field
|
||||
if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) {
|
||||
tested_by!(goto_definition_works_for_named_fields);
|
||||
|
||||
let infer_result = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast);
|
||||
|
||||
let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast);
|
||||
if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) {
|
||||
if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() {
|
||||
let hir_path = hir::Path::from_name_ref(name_ref);
|
||||
let hir_name = hir_path.as_ident().unwrap();
|
||||
|
||||
if let Some(expr) = struct_lit.and_then(|lit| source_map.node_expr(lit.into())) {
|
||||
let ty = infer_result[expr].clone();
|
||||
if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() {
|
||||
let hir_path = hir::Path::from_name_ref(name_ref);
|
||||
let hir_name = hir_path.as_ident().unwrap();
|
||||
|
||||
if let Some(field) = s.field(db, hir_name) {
|
||||
return Exact(NavigationTarget::from_field(db, field));
|
||||
}
|
||||
if let Some(field) = s.field(db, hir_name) {
|
||||
return Exact(NavigationTarget::from_field(db, field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try name resolution
|
||||
let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax());
|
||||
if let Some(path) =
|
||||
name_ref.syntax().ancestors().find_map(ast::Path::cast).and_then(hir::Path::from_ast)
|
||||
{
|
||||
let resolved = resolver.resolve_path(db, &path);
|
||||
match resolved.clone().take_types().or_else(|| resolved.take_values()) {
|
||||
Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
|
||||
Some(Resolution::LocalBinding(pat)) => {
|
||||
let body = resolver.body().expect("no body for local binding");
|
||||
let source_map = body.owner().body_source_map(db);
|
||||
let ptr = source_map.pat_syntax(pat).expect("pattern not found in syntax mapping");
|
||||
let name =
|
||||
path.as_ident().cloned().expect("local binding from a multi-segment path");
|
||||
let ptr = ptr.either(|it| it.into(), |it| it.into());
|
||||
let nav = NavigationTarget::from_scope_entry(file_id, name, ptr);
|
||||
return Exact(nav);
|
||||
}
|
||||
Some(Resolution::GenericParam(..)) => {
|
||||
// FIXME: go to the generic param def
|
||||
}
|
||||
Some(Resolution::SelfType(impl_block)) => {
|
||||
let ty = impl_block.target_ty(db);
|
||||
|
||||
if let Some((def_id, _)) = ty.as_adt() {
|
||||
return Exact(NavigationTarget::from_adt_def(db, def_id));
|
||||
// General case, a path or a local:
|
||||
if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
|
||||
if let Some(resolved) = analyzer.resolve_path(db, path) {
|
||||
match resolved {
|
||||
hir::PathResolution::Def(def) => return Exact(NavigationTarget::from_def(db, def)),
|
||||
hir::PathResolution::LocalBinding(pat) => {
|
||||
if let Some(pat) = analyzer.pat_syntax(db, pat) {
|
||||
let nav = NavigationTarget::from_pat(db, file_id, pat);
|
||||
return Exact(nav);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// If we failed to resolve then check associated items
|
||||
if let Some(function) = function {
|
||||
// Resolve associated item for path expressions
|
||||
if let Some(path_expr) =
|
||||
name_ref.syntax().ancestors().find_map(ast::PathExpr::cast)
|
||||
{
|
||||
let infer_result = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
hir::PathResolution::GenericParam(..) => {
|
||||
// FIXME: go to the generic param def
|
||||
}
|
||||
hir::PathResolution::SelfType(impl_block) => {
|
||||
let ty = impl_block.target_ty(db);
|
||||
|
||||
if let Some(expr) = ast::Expr::cast(path_expr.syntax()) {
|
||||
if let Some(res) = source_map
|
||||
.node_expr(expr)
|
||||
.and_then(|it| infer_result.assoc_resolutions_for_expr(it.into()))
|
||||
{
|
||||
return Exact(NavigationTarget::from_impl_item(db, res));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve associated item for path patterns
|
||||
if let Some(path_pat) =
|
||||
name_ref.syntax().ancestors().find_map(ast::PathPat::cast)
|
||||
{
|
||||
let infer_result = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
|
||||
let pat: &ast::Pat = path_pat.into();
|
||||
|
||||
if let Some(res) = source_map
|
||||
.node_pat(pat)
|
||||
.and_then(|it| infer_result.assoc_resolutions_for_pat(it.into()))
|
||||
{
|
||||
return Exact(NavigationTarget::from_impl_item(db, res));
|
||||
}
|
||||
if let Some((def_id, _)) = ty.as_adt() {
|
||||
return Exact(NavigationTarget::from_adt_def(db, def_id));
|
||||
}
|
||||
}
|
||||
hir::PathResolution::AssocItem(assoc) => {
|
||||
return Exact(NavigationTarget::from_impl_item(db, assoc))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If that fails try the index based approach.
|
||||
// Fallback index based approach:
|
||||
let navs = crate::symbol_index::index_resolve(db, name_ref)
|
||||
.into_iter()
|
||||
.map(NavigationTarget::from_symbol)
|
||||
|
|
|
@ -132,17 +132,15 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
|
|||
.ancestors()
|
||||
.take_while(|it| it.range() == leaf_node.range())
|
||||
.find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())?;
|
||||
let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?;
|
||||
let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?;
|
||||
let infer = function.infer(db);
|
||||
let source_map = function.body_source_map(db);
|
||||
if let Some(expr) = ast::Expr::cast(node).and_then(|e| source_map.node_expr(e)) {
|
||||
Some(infer[expr].display(db).to_string())
|
||||
} else if let Some(pat) = ast::Pat::cast(node).and_then(|p| source_map.node_pat(p)) {
|
||||
Some(infer[pat].display(db).to_string())
|
||||
let analyzer = hir::SourceAnalyser::new(db, frange.file_id, node);
|
||||
let ty = if let Some(ty) = ast::Expr::cast(node).and_then(|e| analyzer.type_of(db, e)) {
|
||||
ty
|
||||
} else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, p)) {
|
||||
ty
|
||||
} else {
|
||||
None
|
||||
}
|
||||
return None;
|
||||
};
|
||||
Some(ty.display(db).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue