aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/name_ref_kind.rs
blob: aff03464ae7b4377173081165e80a00557a4e554 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use hir::Either;
use ra_syntax::{ast, AstNode, AstPtr};
use test_utils::tested_by;

use crate::db::RootDatabase;

pub enum NameRefKind {
    Method(hir::Function),
    Macro(hir::MacroDef),
    FieldAccess(hir::StructField),
    AssocItem(hir::AssocItem),
    Def(hir::ModuleDef),
    SelfType(hir::Ty),
    Pat(AstPtr<ast::BindPat>),
    SelfParam(AstPtr<ast::SelfParam>),
    GenericParam(u32),
}

pub(crate) fn classify_name_ref(
    db: &RootDatabase,
    analyzer: &hir::SourceAnalyzer,
    name_ref: &ast::NameRef,
) -> Option<NameRefKind> {
    use NameRefKind::*;

    // 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 Some(Method(func));
        }
    }

    // It could be a macro call
    if let Some(macro_call) = name_ref
        .syntax()
        .parent()
        .and_then(|node| node.parent())
        .and_then(|node| node.parent())
        .and_then(ast::MacroCall::cast)
    {
        tested_by!(goto_definition_works_for_macros);
        if let Some(mac) = analyzer.resolve_macro_call(db, &macro_call) {
            return Some(Macro(mac));
        }
    }

    // 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 Some(FieldAccess(field));
        };
    }

    // It could also be a named field
    if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::RecordField::cast) {
        tested_by!(goto_definition_works_for_record_fields);

        let record_lit = field_expr.syntax().ancestors().find_map(ast::RecordLit::cast);

        if let Some(ty) = record_lit.and_then(|lit| analyzer.type_of(db, &lit.into())) {
            if let Some((hir::Adt::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 Some(FieldAccess(field));
                }
            }
        }
    }

    // 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) {
            return match resolved {
                hir::PathResolution::Def(def) => Some(Def(def)),
                hir::PathResolution::LocalBinding(Either::A(pat)) => Some(Pat(pat)),
                hir::PathResolution::LocalBinding(Either::B(par)) => Some(SelfParam(par)),
                hir::PathResolution::GenericParam(par) => {
                    // FIXME: get generic param def
                    Some(GenericParam(par))
                }
                hir::PathResolution::Macro(def) => Some(Macro(def)),
                hir::PathResolution::SelfType(impl_block) => {
                    let ty = impl_block.target_ty(db);
                    Some(SelfType(ty))
                }
                hir::PathResolution::AssocItem(assoc) => Some(AssocItem(assoc)),
            };
        }
    }

    None
}