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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! `Semantics` provides the means to get semantic information from syntax trees that are not
//! necessarily part of the compilation process. This is useful when you want to extract information
//! from a modified source file in the context of the current state.
//!
//! Our compilation databases (e.g. `HirDatabase`) provides a lot of steps to go from a syntax tree
//! (as provided by the [`mun_syntax::ast`] module) to more abstract representations of the source
//! through the process of `lowering`. However, for IDE purposes we often want to cut through all
//! this and go from source locations straight to lowered data structures and back. This is what
//! [`Semantics`] enables.

mod source_to_def;

use crate::{
    ids::{DefWithBodyId, ItemDefinitionId},
    resolve,
    resolve::HasResolver,
    semantics::source_to_def::{SourceToDefCache, SourceToDefContainer, SourceToDefContext},
    source_analyzer::SourceAnalyzer,
    FileId, HirDatabase, InFile, ModuleDef, Name, PatId, PerNs, Resolver, Ty, Visibility,
};
use mun_syntax::{ast, AstNode, SyntaxNode, TextSize};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use std::cell::RefCell;

/// The primary API to get semantic information, like types, from syntax trees. Exposes the database
/// it was created with through the `db` field.
pub struct Semantics<'db> {
    pub db: &'db dyn HirDatabase,

    /// Cache of root syntax nodes to their `FileId`
    source_file_to_file: RefCell<FxHashMap<SyntaxNode, FileId>>,

    /// A cache to map source locations to definitions
    source_to_definition_cache: RefCell<SourceToDefCache>,
}

impl<'db> Semantics<'db> {
    /// Constructs a new `Semantics` instance with the given database.
    pub fn new(db: &'db dyn HirDatabase) -> Self {
        Self {
            db,
            source_file_to_file: Default::default(),
            source_to_definition_cache: Default::default(),
        }
    }

    /// Returns the Concrete Syntax Tree for the file with the given `file_id`.
    pub fn parse(&self, file_id: FileId) -> ast::SourceFile {
        let tree = self.db.parse(file_id).tree();
        let mut cache = self.source_file_to_file.borrow_mut();
        cache.insert(tree.syntax().clone(), file_id);
        tree
    }

    /// Computes the `SemanticScope` at the given position in a CST.
    pub fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> SemanticsScope<'db> {
        let analyzer = self.analyze_with_offset(node, offset);
        SemanticsScope {
            db: self.db,
            file_id: analyzer.file_id,
            resolver: analyzer.resolver,
        }
    }

    /// Returns the type of the given expression
    pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Ty> {
        self.analyze(expr.syntax()).type_of_expr(self.db, expr)
    }

    /// Returns the source analyzer for the given node.
    fn analyze(&self, node: &SyntaxNode) -> SourceAnalyzer {
        self.build_analyzer(node, None)
    }

    /// Constructs a `SourceAnalyzer` for the token at the given offset.
    fn analyze_with_offset(&self, node: &SyntaxNode, offset: TextSize) -> SourceAnalyzer {
        self.build_analyzer(node, Some(offset))
    }

    /// Internal function that constructs a `SourceAnalyzer` from the given `node` and optional
    /// `offset` in the file.
    fn build_analyzer(&self, node: &SyntaxNode, offset: Option<TextSize>) -> SourceAnalyzer {
        let node = self.find_file(node.clone());
        let node = node.as_ref();

        // Find the "lowest" container that contains the code
        let container = match self.with_source_to_def_context(|ctx| ctx.find_container(node)) {
            Some(it) => it,
            None => return SourceAnalyzer::new_for_resolver(Resolver::default(), node),
        };

        // Construct an analyzer for the given container
        let resolver = match container {
            SourceToDefContainer::DefWithBodyId(def) => {
                return SourceAnalyzer::new_for_body(self.db, def, node, offset)
            }
            SourceToDefContainer::ModuleId(id) => id.resolver(self.db.upcast()),
        };

        SourceAnalyzer::new_for_resolver(resolver, node)
    }

    /// Runs a function with a `SourceToDefContext` which can be used to cache definition queries.
    fn with_source_to_def_context<F: FnOnce(&mut SourceToDefContext) -> T, T>(&self, f: F) -> T {
        let mut cache = self.source_to_definition_cache.borrow_mut();
        let mut context = SourceToDefContext {
            db: self.db,
            cache: &mut cache,
        };
        f(&mut context)
    }

    /// Returns the file that is associated with the given root CST node or `None` if no such
    /// association exists.
    fn lookup_file(&self, root_node: &SyntaxNode) -> Option<FileId> {
        let cache = self.source_file_to_file.borrow();
        cache.get(root_node).copied()
    }

    /// Decorates the specified node with the file that it is associated with.
    fn find_file(&self, node: SyntaxNode) -> InFile<SyntaxNode> {
        let root_node = find_root(&node);
        let file_id = self.lookup_file(&root_node).unwrap_or_else(|| {
            panic!(
                "\n\nFailed to lookup {:?} in this Semantics.\n\
                 Make sure to use only query nodes, derived from this instance of Semantics.\n\
                 root node:   {:?}\n\
                 known nodes: {}\n\n",
                node,
                root_node,
                self.source_file_to_file
                    .borrow()
                    .keys()
                    .map(|it| format!("{:?}", it))
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        });
        InFile::new(file_id, node)
    }
}

/// Returns the root node of the specified node.
fn find_root(node: &SyntaxNode) -> SyntaxNode {
    node.ancestors().last().unwrap()
}

/// Represents the notion of a scope (set of possible names) at a particular position in source.
pub struct SemanticsScope<'a> {
    pub db: &'a dyn HirDatabase,
    file_id: FileId,
    resolver: Resolver,
}

/// Represents an element in a scope
pub enum ScopeDef {
    ModuleDef(ModuleDef),
    Local(Local),
    Unknown,
}

impl ScopeDef {
    /// Returns all the `ScopeDef`s from a `PerNs`. Never returns duplicates.
    pub fn all_items(def: PerNs<(ItemDefinitionId, Visibility)>) -> SmallVec<[Self; 2]> {
        let mut items = SmallVec::new();
        match (def.take_types(), def.take_values()) {
            (Some(ty), None) => items.push(ScopeDef::ModuleDef(ty.0.into())),
            (None, Some(val)) => items.push(ScopeDef::ModuleDef(val.0.into())),
            (Some(ty), Some(val)) => {
                // Some things are returned as both a value and a type, such as a unit struct.
                items.push(ScopeDef::ModuleDef(ty.0.into()));
                if ty != val {
                    items.push(ScopeDef::ModuleDef(val.0.into()))
                }
            }
            (None, None) => {}
        };

        if items.is_empty() {
            items.push(ScopeDef::Unknown)
        }

        items
    }
}

/// A local variable in a body
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Local {
    pub(crate) parent: DefWithBodyId,
    pub(crate) pat_id: PatId,
}

impl Local {
    /// Returns the type of this local
    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
        let infer = db.infer(self.parent);
        infer[self.pat_id].clone()
    }
}

impl<'a> SemanticsScope<'a> {
    /// Call the `visit` function for every named item in the scope
    pub fn visit_all_names(&self, visit: &mut dyn FnMut(Name, ScopeDef)) {
        let resolver = &self.resolver;

        resolver.visit_all_names(self.db.upcast(), &mut |name, def| {
            let def = match def {
                resolve::ScopeDef::PerNs(it) => {
                    let items = ScopeDef::all_items(it);
                    for item in items {
                        visit(name.clone(), item);
                    }
                    return;
                }
                resolve::ScopeDef::Local(pat_id) => {
                    let parent = resolver
                        .body_owner()
                        .expect("found a local outside of a body");
                    ScopeDef::Local(Local { parent, pat_id })
                }
            };
            visit(name, def)
        })
    }
}