aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/source_binder.rs
blob: ce2a0f2e8947b49973527208a2c3f840f367ac4a (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
97
98
99
/// Lookup hir elements using position in the source code. This is a lossy
/// transformation: in general, a single source might correspond to several
/// modules, functions, etc, due to macros, cfgs and `#[path=]` attributes on
/// modules.
///
/// So, this modules should not be used during hir construction, it exists
/// purely for "IDE needs".
use ra_db::{FileId, FilePosition, Cancelable};
use ra_editor::find_node_at_offset;
use ra_syntax::{
    ast::{self, AstNode},
    SyntaxNodeRef,
};

use crate::{
    HirDatabase, Module, Function, SourceItemId,
    module::ModuleSource,
    DefKind, DefLoc
};

/// Locates the module by `FileId`. Picks topmost module in the file.
pub fn module_from_file_id(db: &impl HirDatabase, file_id: FileId) -> Cancelable<Option<Module>> {
    let module_source = ModuleSource::new_file(file_id);
    module_from_source(db, module_source)
}

/// Locates the module by position in the source code.
pub fn module_from_position(
    db: &impl HirDatabase,
    position: FilePosition,
) -> Cancelable<Option<Module>> {
    let file = db.source_file(position.file_id);
    let module_source = match find_node_at_offset::<ast::Module>(file.syntax(), position.offset) {
        Some(m) if !m.has_semi() => ModuleSource::new_inline(db, position.file_id, m),
        _ => ModuleSource::new_file(position.file_id),
    };
    module_from_source(db, module_source)
}

/// Locates the module by child syntax element within the module
pub fn module_from_child_node(
    db: &impl HirDatabase,
    file_id: FileId,
    child: SyntaxNodeRef,
) -> Cancelable<Option<Module>> {
    let module_source = if let Some(m) = child
        .ancestors()
        .filter_map(ast::Module::cast)
        .find(|it| !it.has_semi())
    {
        ModuleSource::new_inline(db, file_id, m)
    } else {
        ModuleSource::new_file(file_id)
    };
    module_from_source(db, module_source)
}

fn module_from_source(
    db: &impl HirDatabase,
    module_source: ModuleSource,
) -> Cancelable<Option<Module>> {
    let source_root_id = db.file_source_root(module_source.file_id());
    let module_tree = db.module_tree(source_root_id)?;
    let m = module_tree
        .modules_with_sources()
        .find(|(_id, src)| src == &module_source);
    let module_id = ctry!(m).0;
    Ok(Some(Module::new(db, source_root_id, module_id)?))
}

pub fn function_from_source(
    db: &impl HirDatabase,
    file_id: FileId,
    fn_def: ast::FnDef,
) -> Cancelable<Option<Function>> {
    let module = ctry!(module_from_child_node(db, file_id, fn_def.syntax())?);
    let file_items = db.file_items(file_id);
    let item_id = file_items.id_of(file_id, fn_def.syntax());
    let source_item_id = SourceItemId {
        file_id,
        item_id: Some(item_id),
    };
    let def_loc = DefLoc {
        kind: DefKind::Function,
        source_root_id: module.source_root_id,
        module_id: module.module_id,
        source_item_id,
    };
    Ok(Some(Function::new(def_loc.id(db))))
}

pub fn function_from_child_node(
    db: &impl HirDatabase,
    file_id: FileId,
    node: SyntaxNodeRef,
) -> Cancelable<Option<Function>> {
    let fn_def = ctry!(node.ancestors().find_map(ast::FnDef::cast));
    function_from_source(db, file_id, fn_def)
}