403: initial support for macros r=matklad a=matklad

I'll write a more comprehensive description when this is closer to being done. Basically this investigates one question: "how do we represent code which is a result of a macro call". This is an interesting question: currently everything is `FileId` based, but macro expansion does not have a file!

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-01-02 13:05:54 +00:00
commit afa972e78d
22 changed files with 778 additions and 344 deletions

View file

@ -27,7 +27,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) ->
match res.import {
None => true,
Some(import) => {
let range = import.range(ctx.db, module.source().file_id());
let range = import.range(ctx.db, module.file_id());
!range.is_subrange(&ctx.leaf.range())
}
}

View file

@ -1,7 +1,6 @@
use std::{fmt, sync::Arc};
use salsa::{self, Database};
use ra_db::{LocationIntener, BaseDatabase};
use hir::{self, DefId, DefLoc};
use crate::{
symbol_index,
@ -15,7 +14,8 @@ pub(crate) struct RootDatabase {
#[derive(Default)]
struct IdMaps {
defs: LocationIntener<DefLoc, DefId>,
defs: LocationIntener<hir::DefLoc, hir::DefId>,
macros: LocationIntener<hir::MacroCallLoc, hir::MacroCallId>,
}
impl fmt::Debug for IdMaps {
@ -59,12 +59,18 @@ impl salsa::ParallelDatabase for RootDatabase {
impl BaseDatabase for RootDatabase {}
impl AsRef<LocationIntener<DefLoc, DefId>> for RootDatabase {
fn as_ref(&self) -> &LocationIntener<DefLoc, DefId> {
impl AsRef<LocationIntener<hir::DefLoc, hir::DefId>> for RootDatabase {
fn as_ref(&self) -> &LocationIntener<hir::DefLoc, hir::DefId> {
&self.id_maps.defs
}
}
impl AsRef<LocationIntener<hir::MacroCallLoc, hir::MacroCallId>> for RootDatabase {
fn as_ref(&self) -> &LocationIntener<hir::MacroCallLoc, hir::MacroCallId> {
&self.id_maps.macros
}
}
salsa::database_storage! {
pub(crate) struct RootDatabaseStorage for RootDatabase {
impl ra_db::FilesDatabase {
@ -85,6 +91,8 @@ salsa::database_storage! {
fn library_symbols() for symbol_index::LibrarySymbolsQuery;
}
impl hir::db::HirDatabase {
fn hir_source_file() for hir::db::HirSourceFileQuery;
fn expand_macro_invocation() for hir::db::ExpandMacroCallQuery;
fn module_tree() for hir::db::ModuleTreeQuery;
fn fn_scopes() for hir::db::FnScopesQuery;
fn file_items() for hir::db::SourceFileItemsQuery;

View file

@ -18,15 +18,15 @@ pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRang
}
fn extend_selection_in_macro(
db: &RootDatabase,
_db: &RootDatabase,
source_file: &SourceFileNode,
frange: FileRange,
) -> Option<TextRange> {
let macro_call = find_macro_call(source_file.syntax(), frange.range)?;
let exp = crate::macros::expand(db, frange.file_id, macro_call)?;
let dst_range = exp.map_range_forward(frange.range)?;
let dst_range = ra_editor::extend_selection(exp.source_file().syntax(), dst_range)?;
let src_range = exp.map_range_back(dst_range)?;
let (off, exp) = hir::MacroDef::ast_expand(macro_call)?;
let dst_range = exp.map_range_forward(frange.range - off)?;
let dst_range = ra_editor::extend_selection(exp.syntax().borrowed(), dst_range)?;
let src_range = exp.map_range_back(dst_range)? + off;
Some(src_range)
}

View file

@ -230,7 +230,7 @@ impl AnalysisImpl {
Some(it) => it,
};
let root = descr.crate_root();
let file_id = root.source().file_id();
let file_id = root.file_id();
let crate_graph = self.db.crate_graph();
let crate_id = crate_graph.crate_id_for_crate_root(file_id);
@ -283,7 +283,7 @@ impl AnalysisImpl {
if let Some(child_module) =
source_binder::module_from_declaration(&*self.db, position.file_id, module)?
{
let file_id = child_module.source().file_id();
let file_id = child_module.file_id();
let name = match child_module.name() {
Some(name) => name.to_string().into(),
None => "".into(),

View file

@ -19,7 +19,6 @@ mod runnables;
mod extend_selection;
mod syntax_highlighting;
mod macros;
use std::{fmt, sync::Arc};

View file

@ -1,75 +0,0 @@
/// Begining of macro expansion.
///
/// This code should be moved out of ra_analysis into hir (?) ideally.
use ra_syntax::{ast, AstNode, SourceFileNode, TextRange};
use crate::{db::RootDatabase, FileId};
pub(crate) fn expand(
_db: &RootDatabase,
_file_id: FileId,
macro_call: ast::MacroCall,
) -> Option<MacroExpansion> {
let path = macro_call.path()?;
if path.qualifier().is_some() {
return None;
}
let name_ref = path.segment()?.name_ref()?;
if name_ref.text() != "ctry" {
return None;
}
let arg = macro_call.token_tree()?;
let text = format!(
r"
fn dummy() {{
match {} {{
None => return Ok(None),
Some(it) => it,
}}
}}",
arg.syntax().text()
);
let file = SourceFileNode::parse(&text);
let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?;
let match_arg = match_expr.expr()?;
let ranges_map = vec![(arg.syntax().range(), match_arg.syntax().range())];
let res = MacroExpansion {
source_file: file,
ranges_map,
};
Some(res)
}
pub(crate) struct MacroExpansion {
pub(crate) source_file: SourceFileNode,
pub(crate) ranges_map: Vec<(TextRange, TextRange)>,
}
impl MacroExpansion {
pub(crate) fn source_file(&self) -> &SourceFileNode {
&self.source_file
}
pub(crate) fn map_range_back(&self, tgt_range: TextRange) -> Option<TextRange> {
for (s_range, t_range) in self.ranges_map.iter() {
if tgt_range.is_subrange(&t_range) {
let tgt_at_zero_range = tgt_range - tgt_range.start();
let tgt_range_offset = tgt_range.start() - t_range.start();
let src_range = tgt_at_zero_range + tgt_range_offset + s_range.start();
return Some(src_range);
}
}
None
}
pub(crate) fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> {
for (s_range, t_range) in self.ranges_map.iter() {
if src_range.is_subrange(&s_range) {
let src_at_zero_range = src_range - src_range.start();
let src_range_offset = src_range.start() - s_range.start();
let src_range = src_at_zero_range + src_range_offset + t_range.start();
return Some(src_range);
}
}
None
}
}

View file

@ -15,13 +15,13 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<Hi
.descendants()
.filter_map(ast::MacroCall::cast)
{
if let Some(exp) = crate::macros::expand(db, file_id, macro_call) {
let mapped_ranges = ra_editor::highlight(exp.source_file().syntax())
if let Some((off, exp)) = hir::MacroDef::ast_expand(macro_call) {
let mapped_ranges = ra_editor::highlight(exp.syntax().borrowed())
.into_iter()
.filter_map(|r| {
let mapped_range = exp.map_range_back(r.range)?;
let res = HighlightedRange {
range: mapped_range,
range: mapped_range + off,
tag: r.tag,
};
Some(res)
@ -44,7 +44,7 @@ mod tests {
fn main() {
ctry!({ let x = 92; x});
}
",
",
);
let highlights = analysis.highlight(file_id).unwrap();
assert_eq_dbg(
@ -60,4 +60,26 @@ mod tests {
&highlights,
)
}
// FIXME: this test is not really necessary: artifact of the inital hacky
// macros implementation.
#[test]
fn highlight_query_group_macro() {
let (analysis, file_id) = single_file(
"
salsa::query_group! {
pub trait HirDatabase: SyntaxDatabase {}
}
",
);
let highlights = analysis.highlight(file_id).unwrap();
assert_eq_dbg(
r#"[HighlightedRange { range: [20; 32), tag: "macro" },
HighlightedRange { range: [13; 18), tag: "text" },
HighlightedRange { range: [51; 54), tag: "keyword" },
HighlightedRange { range: [55; 60), tag: "keyword" },
HighlightedRange { range: [61; 72), tag: "function" }]"#,
&highlights,
)
}
}

View file

@ -1,9 +1,10 @@
use std::sync::Arc;
use ra_db::Cancelable;
use ra_syntax::ast::{self, NameOwner, StructFlavor};
use crate::{
DefId, Cancelable, Name, AsName,
DefId, Name, AsName,
db::HirDatabase,
type_ref::TypeRef,
};

View file

@ -1,13 +1,14 @@
use std::sync::Arc;
use ra_syntax::SyntaxNode;
use ra_db::{SourceRootId, LocationIntener, SyntaxDatabase, FileId, Cancelable};
use ra_syntax::{SyntaxNode, SourceFileNode};
use ra_db::{SourceRootId, LocationIntener, SyntaxDatabase, Cancelable};
use crate::{
DefLoc, DefId, Name,
DefLoc, DefId, MacroCallLoc, MacroCallId, Name, HirFileId,
SourceFileItems, SourceItemId,
query_definitions,
FnScopes,
macros::MacroExpansion,
module::{ModuleId, ModuleTree, ModuleSource,
nameres::{ItemMap, InputModuleItems}},
ty::{InferenceResult, Ty},
@ -18,7 +19,17 @@ salsa::query_group! {
pub trait HirDatabase: SyntaxDatabase
+ AsRef<LocationIntener<DefLoc, DefId>>
+ AsRef<LocationIntener<MacroCallLoc, MacroCallId>>
{
fn hir_source_file(file_id: HirFileId) -> SourceFileNode {
type HirSourceFileQuery;
use fn HirFileId::hir_source_file;
}
fn expand_macro_invocation(invoc: MacroCallId) -> Option<Arc<MacroExpansion>> {
type ExpandMacroCallQuery;
use fn crate::macros::expand_macro_invocation;
}
fn fn_scopes(def_id: DefId) -> Arc<FnScopes> {
type FnScopesQuery;
use fn query_definitions::fn_scopes;
@ -49,7 +60,7 @@ pub trait HirDatabase: SyntaxDatabase
use fn crate::ty::type_for_field;
}
fn file_items(file_id: FileId) -> Arc<SourceFileItems> {
fn file_items(file_id: HirFileId) -> Arc<SourceFileItems> {
type SourceFileItemsQuery;
use fn query_definitions::file_items;
}

280
crates/ra_hir/src/ids.rs Normal file
View file

@ -0,0 +1,280 @@
use ra_db::{SourceRootId, LocationIntener, Cancelable, FileId};
use ra_syntax::{SourceFileNode, SyntaxKind, SyntaxNode, SyntaxNodeRef, SourceFile, AstNode, ast};
use crate::{
HirDatabase, PerNs, ModuleId, Module, Def, Function, Struct, Enum,
arena::{Arena, Id},
};
/// hir makes a heavy use of ids: integer (u32) handlers to various things. You
/// can think of id as a pointer (but without a lifetime) or a file descriptor
/// (but for hir objects).
///
/// This module defines a bunch of ids we are using. The most important ones are
/// probably `HirFileId` and `DefId`.
/// Input to the analyzer is a set of file, where each file is indetified by
/// `FileId` and contains source code. However, another source of source code in
/// Rust are macros: each macro can be thought of as producing a "temporary
/// file". To assign id to such file, we use the id of a macro call that
/// produced the file. So, a `HirFileId` is either a `FileId` (source code
/// written by user), or a `MacroCallId` (source code produced by macro).
///
/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file containin
/// the call plus the offset of the macro call in the file. Note that this is a
/// recursive definition! Nethetheless, size_of of `HirFileId` is finite
/// (because everything bottoms out at the real `FileId`) and small
/// (`MacroCallId` uses location interner).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HirFileId(HirFileIdRepr);
impl HirFileId {
/// For macro-expansion files, returns the file original source file the
/// expansionoriginated from.
pub(crate) fn original_file(self, db: &impl HirDatabase) -> FileId {
match self.0 {
HirFileIdRepr::File(file_id) => file_id,
HirFileIdRepr::Macro(macro_call_id) => {
let loc = macro_call_id.loc(db);
loc.source_item_id.file_id.original_file(db)
}
}
}
pub(crate) fn as_original_file(self) -> FileId {
match self.0 {
HirFileIdRepr::File(file_id) => file_id,
HirFileIdRepr::Macro(_r) => panic!("macro generated file: {:?}", self),
}
}
pub(crate) fn hir_source_file(db: &impl HirDatabase, file_id: HirFileId) -> SourceFileNode {
match file_id.0 {
HirFileIdRepr::File(file_id) => db.source_file(file_id),
HirFileIdRepr::Macro(m) => {
if let Some(exp) = db.expand_macro_invocation(m) {
return exp.file();
}
// returning an empty string looks fishy...
SourceFileNode::parse("")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum HirFileIdRepr {
File(FileId),
Macro(MacroCallId),
}
impl From<FileId> for HirFileId {
fn from(file_id: FileId) -> HirFileId {
HirFileId(HirFileIdRepr::File(file_id))
}
}
impl From<MacroCallId> for HirFileId {
fn from(macro_call_id: MacroCallId) -> HirFileId {
HirFileId(HirFileIdRepr::Macro(macro_call_id))
}
}
/// `MacroCallId` identifies a particular macro invocation, like
/// `println!("Hello, {}", world)`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MacroCallId(u32);
ra_db::impl_numeric_id!(MacroCallId);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MacroCallLoc {
pub(crate) source_root_id: SourceRootId,
pub(crate) module_id: ModuleId,
pub(crate) source_item_id: SourceItemId,
}
impl MacroCallId {
pub(crate) fn loc(
self,
db: &impl AsRef<LocationIntener<MacroCallLoc, MacroCallId>>,
) -> MacroCallLoc {
db.as_ref().id2loc(self)
}
}
impl MacroCallLoc {
#[allow(unused)]
pub(crate) fn id(
&self,
db: &impl AsRef<LocationIntener<MacroCallLoc, MacroCallId>>,
) -> MacroCallId {
db.as_ref().loc2id(&self)
}
}
/// Def's are a core concept of hir. A `Def` is an Item (function, module, etc)
/// in a specific module.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DefId(u32);
ra_db::impl_numeric_id!(DefId);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DefLoc {
pub(crate) kind: DefKind,
pub(crate) source_root_id: SourceRootId,
pub(crate) module_id: ModuleId,
pub(crate) source_item_id: SourceItemId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum DefKind {
Module,
Function,
Struct,
Enum,
Item,
StructCtor,
}
impl DefId {
pub(crate) fn loc(self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefLoc {
db.as_ref().id2loc(self)
}
pub fn resolve(self, db: &impl HirDatabase) -> Cancelable<Def> {
let loc = self.loc(db);
let res = match loc.kind {
DefKind::Module => {
let module = Module::new(db, loc.source_root_id, loc.module_id)?;
Def::Module(module)
}
DefKind::Function => {
let function = Function::new(self);
Def::Function(function)
}
DefKind::Struct => {
let struct_def = Struct::new(self);
Def::Struct(struct_def)
}
DefKind::Enum => {
let enum_def = Enum::new(self);
Def::Enum(enum_def)
}
DefKind::StructCtor => Def::Item,
DefKind::Item => Def::Item,
};
Ok(res)
}
/// For a module, returns that module; for any other def, returns the containing module.
pub fn module(self, db: &impl HirDatabase) -> Cancelable<Module> {
let loc = self.loc(db);
Module::new(db, loc.source_root_id, loc.module_id)
}
}
impl DefLoc {
pub(crate) fn id(&self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefId {
db.as_ref().loc2id(&self)
}
}
impl DefKind {
pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> PerNs<DefKind> {
match kind {
SyntaxKind::FN_DEF => PerNs::values(DefKind::Function),
SyntaxKind::MODULE => PerNs::types(DefKind::Module),
SyntaxKind::STRUCT_DEF => PerNs::both(DefKind::Struct, DefKind::StructCtor),
SyntaxKind::ENUM_DEF => PerNs::types(DefKind::Enum),
// These define items, but don't have their own DefKinds yet:
SyntaxKind::TRAIT_DEF => PerNs::types(DefKind::Item),
SyntaxKind::TYPE_DEF => PerNs::types(DefKind::Item),
SyntaxKind::CONST_DEF => PerNs::values(DefKind::Item),
SyntaxKind::STATIC_DEF => PerNs::values(DefKind::Item),
_ => PerNs::none(),
}
}
}
/// Identifier of item within a specific file. This is stable over reparses, so
/// it's OK to use it as a salsa key/value.
pub(crate) type SourceFileItemId = Id<SyntaxNode>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SourceItemId {
pub(crate) file_id: HirFileId,
/// None for the whole file.
pub(crate) item_id: Option<SourceFileItemId>,
}
/// Maps item's `SyntaxNode`s to `SourceFileItemId` and back.
#[derive(Debug, PartialEq, Eq)]
pub struct SourceFileItems {
file_id: HirFileId,
arena: Arena<SyntaxNode>,
}
impl SourceFileItems {
pub(crate) fn new(file_id: HirFileId, source_file: SourceFile) -> SourceFileItems {
let mut res = SourceFileItems {
file_id,
arena: Arena::default(),
};
res.init(source_file);
res
}
fn init(&mut self, source_file: SourceFile) {
source_file.syntax().descendants().for_each(|it| {
if let Some(module_item) = ast::ModuleItem::cast(it) {
self.alloc(module_item.syntax().owned());
} else if let Some(macro_call) = ast::MacroCall::cast(it) {
self.alloc(macro_call.syntax().owned());
}
});
}
fn alloc(&mut self, item: SyntaxNode) -> SourceFileItemId {
self.arena.alloc(item)
}
pub(crate) fn id_of(&self, file_id: HirFileId, item: SyntaxNodeRef) -> SourceFileItemId {
assert_eq!(
self.file_id, file_id,
"SourceFileItems: wrong file, expected {:?}, got {:?}",
self.file_id, file_id
);
self.id_of_unchecked(item)
}
pub(crate) fn id_of_unchecked(&self, item: SyntaxNodeRef) -> SourceFileItemId {
if let Some((id, _)) = self.arena.iter().find(|(_id, i)| i.borrowed() == item) {
return id;
}
// This should not happen. Let's try to give a sensible diagnostics.
if let Some((id, i)) = self.arena.iter().find(|(_id, i)| i.range() == item.range()) {
// FIXME(#288): whyyy are we getting here?
log::error!(
"unequal syntax nodes with the same range:\n{:?}\n{:?}",
item,
i
);
return id;
}
panic!(
"Can't find {:?} in SourceFileItems:\n{:?}",
item,
self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
);
}
pub fn id_of_source_file(&self) -> SourceFileItemId {
let (id, _syntax) = self.arena.iter().next().unwrap();
id
}
}
impl std::ops::Index<SourceFileItemId> for SourceFileItems {
type Output = SyntaxNode;
fn index(&self, idx: SourceFileItemId) -> &SyntaxNode {
&self.arena[idx]
}
}

View file

@ -1,6 +1,6 @@
pub use ra_db::CrateId;
pub use ra_db::{CrateId, Cancelable};
use crate::{HirDatabase, Module, Cancelable, Name, AsName};
use crate::{HirDatabase, Module, Name, AsName, HirFileId};
/// hir::Crate describes a single crate. It's the main inteface with which
/// crate's dependencies interact. Mostly, it should be just a proxy for the
@ -35,6 +35,7 @@ impl Crate {
let crate_graph = db.crate_graph();
let file_id = crate_graph.crate_root(self.crate_id);
let source_root_id = db.file_source_root(file_id);
let file_id = HirFileId::from(file_id);
let module_tree = db.module_tree(source_root_id)?;
// FIXME: teach module tree about crate roots instead of guessing
let (module_id, _) = ctry!(module_tree

View file

@ -22,7 +22,10 @@ mod path;
mod arena;
pub mod source_binder;
mod ids;
mod macros;
mod name;
// can't use `crate` or `r#crate` here :(
mod krate;
mod module;
mod function;
@ -30,21 +33,18 @@ mod adt;
mod type_ref;
mod ty;
use std::ops::Index;
use ra_syntax::{SyntaxNodeRef, SyntaxNode, SyntaxKind};
use ra_db::{LocationIntener, SourceRootId, FileId, Cancelable};
use crate::{
db::HirDatabase,
arena::{Arena, Id},
name::{AsName, KnownName},
ids::{DefKind, SourceItemId, SourceFileItemId, SourceFileItems},
};
pub use self::{
path::{Path, PathKind},
name::Name,
krate::Crate,
ids::{HirFileId, DefId, DefLoc, MacroCallId, MacroCallLoc},
macros::{MacroDef, MacroInput, MacroExpansion},
module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution},
function::{Function, FnScopes},
adt::{Struct, Enum},
@ -53,60 +53,6 @@ pub use self::{
pub use self::function::FnSignatureInfo;
/// Def's are a core concept of hir. A `Def` is an Item (function, module, etc)
/// in a specific module.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DefId(u32);
ra_db::impl_numeric_id!(DefId);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum DefKind {
Module,
Function,
Struct,
Enum,
Item,
StructCtor,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DefLoc {
pub(crate) kind: DefKind,
source_root_id: SourceRootId,
module_id: ModuleId,
source_item_id: SourceItemId,
}
impl DefKind {
pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> PerNs<DefKind> {
match kind {
SyntaxKind::FN_DEF => PerNs::values(DefKind::Function),
SyntaxKind::MODULE => PerNs::types(DefKind::Module),
SyntaxKind::STRUCT_DEF => PerNs::both(DefKind::Struct, DefKind::StructCtor),
SyntaxKind::ENUM_DEF => PerNs::types(DefKind::Enum),
// These define items, but don't have their own DefKinds yet:
SyntaxKind::TRAIT_DEF => PerNs::types(DefKind::Item),
SyntaxKind::TYPE_DEF => PerNs::types(DefKind::Item),
SyntaxKind::CONST_DEF => PerNs::values(DefKind::Item),
SyntaxKind::STATIC_DEF => PerNs::values(DefKind::Item),
_ => PerNs::none(),
}
}
}
impl DefId {
pub(crate) fn loc(self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefLoc {
db.as_ref().id2loc(self)
}
}
impl DefLoc {
pub(crate) fn id(&self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefId {
db.as_ref().loc2id(&self)
}
}
pub enum Def {
Module(Module),
Function(Function),
@ -114,106 +60,3 @@ pub enum Def {
Enum(Enum),
Item,
}
impl DefId {
pub fn resolve(self, db: &impl HirDatabase) -> Cancelable<Def> {
let loc = self.loc(db);
let res = match loc.kind {
DefKind::Module => {
let module = Module::new(db, loc.source_root_id, loc.module_id)?;
Def::Module(module)
}
DefKind::Function => {
let function = Function::new(self);
Def::Function(function)
}
DefKind::Struct => {
let struct_def = Struct::new(self);
Def::Struct(struct_def)
}
DefKind::Enum => {
let enum_def = Enum::new(self);
Def::Enum(enum_def)
}
DefKind::StructCtor => Def::Item,
DefKind::Item => Def::Item,
};
Ok(res)
}
/// For a module, returns that module; for any other def, returns the containing module.
pub fn module(self, db: &impl HirDatabase) -> Cancelable<Module> {
let loc = self.loc(db);
Module::new(db, loc.source_root_id, loc.module_id)
}
}
/// Identifier of item within a specific file. This is stable over reparses, so
/// it's OK to use it as a salsa key/value.
pub(crate) type SourceFileItemId = Id<SyntaxNode>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SourceItemId {
file_id: FileId,
/// None for the whole file.
item_id: Option<SourceFileItemId>,
}
/// Maps item's `SyntaxNode`s to `SourceFileItemId` and back.
#[derive(Debug, PartialEq, Eq)]
pub struct SourceFileItems {
file_id: FileId,
arena: Arena<SyntaxNode>,
}
impl SourceFileItems {
fn new(file_id: FileId) -> SourceFileItems {
SourceFileItems {
file_id,
arena: Arena::default(),
}
}
fn alloc(&mut self, item: SyntaxNode) -> SourceFileItemId {
self.arena.alloc(item)
}
pub fn id_of(&self, file_id: FileId, item: SyntaxNodeRef) -> SourceFileItemId {
assert_eq!(
self.file_id, file_id,
"SourceFileItems: wrong file, expected {:?}, got {:?}",
self.file_id, file_id
);
self.id_of_unchecked(item)
}
fn id_of_unchecked(&self, item: SyntaxNodeRef) -> SourceFileItemId {
if let Some((id, _)) = self.arena.iter().find(|(_id, i)| i.borrowed() == item) {
return id;
}
// This should not happen. Let's try to give a sensible diagnostics.
if let Some((id, i)) = self.arena.iter().find(|(_id, i)| i.range() == item.range()) {
// FIXME(#288): whyyy are we getting here?
log::error!(
"unequal syntax nodes with the same range:\n{:?}\n{:?}",
item,
i
);
return id;
}
panic!(
"Can't find {:?} in SourceFileItems:\n{:?}",
item,
self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
);
}
pub fn id_of_source_file(&self) -> SourceFileItemId {
let (id, _syntax) = self.arena.iter().next().unwrap();
id
}
}
impl Index<SourceFileItemId> for SourceFileItems {
type Output = SyntaxNode;
fn index(&self, idx: SourceFileItemId) -> &SyntaxNode {
&self.arena[idx]
}
}

181
crates/ra_hir/src/macros.rs Normal file
View file

@ -0,0 +1,181 @@
/// Machinery for macro expansion.
///
/// One of the more complicated things about macros is managing the source code
/// that is produced after expansion. See `HirFileId` and `MacroCallId` for how
/// do we do that.
///
/// When file-management question is resolved, all that is left is a token tree
/// to token tree transformation plus hygent. We don't have either of thouse
/// yet, so all macros are string based at the moment!
use std::sync::Arc;
use ra_db::LocalSyntaxPtr;
use ra_syntax::{
TextRange, TextUnit, SourceFileNode, AstNode, SyntaxNode,
ast::{self, NameOwner},
};
use crate::{HirDatabase, MacroCallId};
// Hard-coded defs for now :-(
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroDef {
CTry,
QueryGroup,
}
impl MacroDef {
/// Expands macro call, returning the expansion and offset to be used to
/// convert ranges between expansion and original source.
pub fn ast_expand(macro_call: ast::MacroCall) -> Option<(TextUnit, MacroExpansion)> {
let (def, input) = MacroDef::from_call(macro_call)?;
let exp = def.expand(input)?;
let off = macro_call.token_tree()?.syntax().range().start();
Some((off, exp))
}
fn from_call(macro_call: ast::MacroCall) -> Option<(MacroDef, MacroInput)> {
let def = {
let path = macro_call.path()?;
let name_ref = path.segment()?.name_ref()?;
if name_ref.text() == "ctry" {
MacroDef::CTry
} else if name_ref.text() == "query_group" {
MacroDef::QueryGroup
} else {
return None;
}
};
let input = {
let arg = macro_call.token_tree()?.syntax();
MacroInput {
text: arg.text().to_string(),
}
};
Some((def, input))
}
fn expand(self, input: MacroInput) -> Option<MacroExpansion> {
match self {
MacroDef::CTry => self.expand_ctry(input),
MacroDef::QueryGroup => self.expand_query_group(input),
}
}
fn expand_ctry(self, input: MacroInput) -> Option<MacroExpansion> {
let text = format!(
r"
fn dummy() {{
match {} {{
None => return Ok(None),
Some(it) => it,
}}
}}",
input.text
);
let file = SourceFileNode::parse(&text);
let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?;
let match_arg = match_expr.expr()?;
let ptr = LocalSyntaxPtr::new(match_arg.syntax());
let src_range = TextRange::offset_len(0.into(), TextUnit::of_str(&input.text));
let ranges_map = vec![(src_range, match_arg.syntax().range())];
let res = MacroExpansion {
text,
ranges_map,
ptr,
};
Some(res)
}
fn expand_query_group(self, input: MacroInput) -> Option<MacroExpansion> {
let anchor = "trait ";
let pos = input.text.find(anchor)? + anchor.len();
let trait_name = input.text[pos..]
.chars()
.take_while(|c| c.is_alphabetic())
.collect::<String>();
if trait_name.is_empty() {
return None;
}
let src_range = TextRange::offset_len((pos as u32).into(), TextUnit::of_str(&trait_name));
let text = format!(r"trait {} {{ }}", trait_name);
let file = SourceFileNode::parse(&text);
let trait_def = file.syntax().descendants().find_map(ast::TraitDef::cast)?;
let name = trait_def.name()?;
let ptr = LocalSyntaxPtr::new(trait_def.syntax());
let ranges_map = vec![(src_range, name.syntax().range())];
let res = MacroExpansion {
text,
ranges_map,
ptr,
};
Some(res)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MacroInput {
// Should be token trees
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MacroExpansion {
/// The result of macro expansion. Should be token tree as well.
text: String,
/// Correspondence between ranges in the original source code and ranges in
/// the macro.
ranges_map: Vec<(TextRange, TextRange)>,
/// Implementation detail: internally, a macro is expanded to the whole file,
/// even if it is an expression. This `ptr` selects the actual expansion from
/// the expanded file.
ptr: LocalSyntaxPtr,
}
impl MacroExpansion {
// FIXME: does not really make sense, macro expansion is not neccessary a
// whole file. See `MacroExpansion::ptr` as well.
pub(crate) fn file(&self) -> SourceFileNode {
SourceFileNode::parse(&self.text)
}
pub fn syntax(&self) -> SyntaxNode {
self.ptr.resolve(&self.file())
}
/// Maps range in the source code to the range in the expanded code.
pub fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> {
for (s_range, t_range) in self.ranges_map.iter() {
if src_range.is_subrange(&s_range) {
let src_at_zero_range = src_range - src_range.start();
let src_range_offset = src_range.start() - s_range.start();
let src_range = src_at_zero_range + src_range_offset + t_range.start();
return Some(src_range);
}
}
None
}
/// Maps range in the expanded code to the range in the source code.
pub fn map_range_back(&self, tgt_range: TextRange) -> Option<TextRange> {
for (s_range, t_range) in self.ranges_map.iter() {
if tgt_range.is_subrange(&t_range) {
let tgt_at_zero_range = tgt_range - tgt_range.start();
let tgt_range_offset = tgt_range.start() - t_range.start();
let src_range = tgt_at_zero_range + tgt_range_offset + s_range.start();
return Some(src_range);
}
}
None
}
}
pub(crate) fn expand_macro_invocation(
db: &impl HirDatabase,
invoc: MacroCallId,
) -> Option<Arc<MacroExpansion>> {
let loc = invoc.loc(db);
let syntax = db.file_item(loc.source_item_id);
let syntax = syntax.borrowed();
let macro_call = ast::MacroCall::cast(syntax).unwrap();
let (def, input) = MacroDef::from_call(macro_call)?;
def.expand(input).map(Arc::new)
}

View file

@ -6,7 +6,7 @@ use ra_db::{LocationIntener, BaseDatabase, FilePosition, FileId, CrateGraph, Sou
use relative_path::RelativePathBuf;
use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset};
use crate::{db, DefId, DefLoc};
use crate::{db, DefId, DefLoc, MacroCallId, MacroCallLoc};
pub const WORKSPACE: SourceRootId = SourceRootId(0);
@ -95,6 +95,7 @@ impl MockDatabase {
#[derive(Debug, Default)]
struct IdMaps {
defs: LocationIntener<DefLoc, DefId>,
macros: LocationIntener<MacroCallLoc, MacroCallId>,
}
impl salsa::Database for MockDatabase {
@ -144,6 +145,11 @@ impl AsRef<LocationIntener<DefLoc, DefId>> for MockDatabase {
&self.id_maps.defs
}
}
impl AsRef<LocationIntener<MacroCallLoc, MacroCallId>> for MockDatabase {
fn as_ref(&self) -> &LocationIntener<MacroCallLoc, MacroCallId> {
&self.id_maps.macros
}
}
impl MockDatabase {
pub(crate) fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<MockDatabase>> {
@ -183,6 +189,8 @@ salsa::database_storage! {
fn file_lines() for ra_db::FileLinesQuery;
}
impl db::HirDatabase {
fn hir_source_file() for db::HirSourceFileQuery;
fn expand_macro_invocation() for db::ExpandMacroCallQuery;
fn module_tree() for db::ModuleTreeQuery;
fn fn_scopes() for db::FnScopesQuery;
fn file_items() for db::SourceFileItemsQuery;

View file

@ -15,6 +15,7 @@ use relative_path::RelativePathBuf;
use crate::{
Def, DefKind, DefLoc, DefId,
Name, Path, PathKind, HirDatabase, SourceItemId, SourceFileItemId, Crate,
HirFileId,
arena::{Arena, Id},
};
@ -48,13 +49,17 @@ impl Module {
/// Returns `None` for the root module
pub fn parent_link_source(&self, db: &impl HirDatabase) -> Option<(FileId, ast::ModuleNode)> {
let link = self.module_id.parent_link(&self.tree)?;
let file_id = link.owner(&self.tree).source(&self.tree).file_id();
let file_id = link
.owner(&self.tree)
.source(&self.tree)
.file_id()
.as_original_file();
let src = link.bind_source(&self.tree, db);
Some((file_id, src))
}
pub fn source(&self) -> ModuleSource {
self.module_id.source(&self.tree)
pub fn file_id(&self) -> FileId {
self.source().file_id().as_original_file()
}
/// Parent module. Returns `None` if this is a root module.
@ -69,7 +74,7 @@ impl Module {
/// Returns the crate this module is part of.
pub fn krate(&self, db: &impl HirDatabase) -> Option<Crate> {
let root_id = self.module_id.crate_root(&self.tree);
let file_id = root_id.source(&self.tree).file_id();
let file_id = root_id.source(&self.tree).file_id().as_original_file();
let crate_graph = db.crate_graph();
let crate_id = crate_graph.crate_id_for_crate_root(file_id)?;
Some(Crate::new(crate_id))
@ -162,6 +167,10 @@ impl Module {
pub fn problems(&self, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> {
self.module_id.problems(&self.tree, db)
}
pub(crate) fn source(&self) -> ModuleSource {
self.module_id.source(&self.tree)
}
}
/// Physically, rust source is organized as a set of files, but logically it is
@ -291,18 +300,18 @@ pub struct ModuleData {
impl ModuleSource {
// precondition: item_id **must** point to module
fn new(file_id: FileId, item_id: Option<SourceFileItemId>) -> ModuleSource {
fn new(file_id: HirFileId, item_id: Option<SourceFileItemId>) -> ModuleSource {
let source_item_id = SourceItemId { file_id, item_id };
ModuleSource(source_item_id)
}
pub(crate) fn new_file(file_id: FileId) -> ModuleSource {
pub(crate) fn new_file(file_id: HirFileId) -> ModuleSource {
ModuleSource::new(file_id, None)
}
pub(crate) fn new_inline(
db: &impl HirDatabase,
file_id: FileId,
file_id: HirFileId,
m: ast::Module,
) -> ModuleSource {
assert!(!m.has_semi());
@ -311,7 +320,7 @@ impl ModuleSource {
ModuleSource::new(file_id, Some(item_id))
}
pub fn file_id(self) -> FileId {
pub(crate) fn file_id(self) -> HirFileId {
self.0.file_id
}

View file

@ -64,7 +64,7 @@ fn create_module_tree<'a>(
let source_root = db.source_root(source_root);
for &file_id in source_root.files.values() {
let source = ModuleSource::new_file(file_id);
let source = ModuleSource::new_file(file_id.into());
if visited.contains(&source) {
continue; // TODO: use explicit crate_roots here
}
@ -123,7 +123,7 @@ fn build_subtree(
visited,
roots,
Some(link),
ModuleSource::new_file(file_id),
ModuleSource::new_file(file_id.into()),
),
})
.collect::<Cancelable<Vec<_>>>()?;
@ -155,7 +155,7 @@ fn resolve_submodule(
name: &Name,
) -> (Vec<FileId>, Option<Problem>) {
// FIXME: handle submodules of inline modules properly
let file_id = source.file_id();
let file_id = source.file_id().original_file(db);
let source_root_id = db.file_source_root(file_id);
let path = db.file_relative_path(file_id);
let root = RelativePathBuf::default();

View file

@ -22,10 +22,10 @@ use ra_syntax::{
SyntaxKind::{self, *},
ast::{self, AstNode}
};
use ra_db::SourceRootId;
use ra_db::{SourceRootId, Cancelable, FileId};
use crate::{
Cancelable, FileId,
HirFileId,
DefId, DefLoc, DefKind,
SourceItemId, SourceFileItemId, SourceFileItems,
Path, PathKind,
@ -70,7 +70,7 @@ pub struct InputModuleItems {
#[derive(Debug, PartialEq, Eq)]
struct ModuleItem {
id: SourceFileItemId,
id: SourceItemId,
name: Name,
kind: SyntaxKind,
vis: Vis,
@ -95,9 +95,11 @@ pub struct NamedImport {
}
impl NamedImport {
// FIXME: this is only here for one use-case in completion. Seems like a
// pretty gross special case.
pub fn range(&self, db: &impl HirDatabase, file_id: FileId) -> TextRange {
let source_item_id = SourceItemId {
file_id,
file_id: file_id.into(),
item_id: Some(self.file_item_id),
};
let syntax = db.file_item(source_item_id);
@ -209,24 +211,28 @@ impl<T> PerNs<T> {
}
impl InputModuleItems {
pub(crate) fn new<'a>(
pub(crate) fn add_item(
&mut self,
file_id: HirFileId,
file_items: &SourceFileItems,
items: impl Iterator<Item = ast::ModuleItem<'a>>,
) -> InputModuleItems {
let mut res = InputModuleItems::default();
for item in items {
res.add_item(file_items, item);
}
res
}
fn add_item(&mut self, file_items: &SourceFileItems, item: ast::ModuleItem) -> Option<()> {
item: ast::ModuleItem,
) -> Option<()> {
match item {
ast::ModuleItem::StructDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::EnumDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::FnDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::TraitDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::TypeDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::StructDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::EnumDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::FnDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::TraitDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::TypeDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::ImplItem(_) => {
// impls don't define items
}
@ -234,9 +240,15 @@ impl InputModuleItems {
ast::ModuleItem::ExternCrateItem(_) => {
// TODO
}
ast::ModuleItem::ConstDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::StaticDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::Module(it) => self.items.push(ModuleItem::new(file_items, it)?),
ast::ModuleItem::ConstDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::StaticDef(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
ast::ModuleItem::Module(it) => {
self.items.push(ModuleItem::new(file_id, file_items, it)?)
}
}
Some(())
}
@ -258,11 +270,16 @@ impl InputModuleItems {
}
impl ModuleItem {
fn new<'a>(file_items: &SourceFileItems, item: impl ast::NameOwner<'a>) -> Option<ModuleItem> {
fn new<'a>(
file_id: HirFileId,
file_items: &SourceFileItems,
item: impl ast::NameOwner<'a>,
) -> Option<ModuleItem> {
let name = item.name()?.as_name();
let kind = item.syntax().kind();
let vis = Vis::Other;
let id = file_items.id_of_unchecked(item.syntax());
let item_id = Some(file_items.id_of_unchecked(item.syntax()));
let id = SourceItemId { file_id, item_id };
let res = ModuleItem {
id,
name,
@ -302,7 +319,7 @@ where
pub(crate) fn resolve(mut self) -> Cancelable<ItemMap> {
for (&module_id, items) in self.input.iter() {
self.populate_module(module_id, items)?;
self.populate_module(module_id, Arc::clone(items))?;
}
for &module_id in self.input.keys() {
@ -312,9 +329,11 @@ where
Ok(self.result)
}
fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) -> Cancelable<()> {
let file_id = module_id.source(&self.module_tree).file_id();
fn populate_module(
&mut self,
module_id: ModuleId,
input: Arc<InputModuleItems>,
) -> Cancelable<()> {
let mut module_items = ModuleScope::default();
// Populate extern crates prelude
@ -322,7 +341,8 @@ where
let root_id = module_id.crate_root(&self.module_tree);
let file_id = root_id.source(&self.module_tree).file_id();
let crate_graph = self.db.crate_graph();
if let Some(crate_id) = crate_graph.crate_id_for_crate_root(file_id) {
if let Some(crate_id) = crate_graph.crate_id_for_crate_root(file_id.as_original_file())
{
let krate = Crate::new(crate_id);
for dep in krate.dependencies(self.db) {
if let Some(module) = dep.krate.root_module(self.db)? {
@ -362,10 +382,7 @@ where
kind: k,
source_root_id: self.source_root,
module_id,
source_item_id: SourceItemId {
file_id,
item_id: Some(item.id),
},
source_item_id: item.id,
};
def_loc.id(self.db)
});

View file

@ -77,6 +77,35 @@ fn item_map_smoke_test() {
);
}
#[test]
fn item_map_contains_items_from_expansions() {
let (item_map, module_id) = item_map(
"
//- /lib.rs
mod foo;
use crate::foo::bar::Baz;
<|>
//- /foo/mod.rs
pub mod bar;
//- /foo/bar.rs
salsa::query_group! {
trait Baz {}
}
",
);
check_module_item_map(
&item_map,
module_id,
"
Baz: t
foo: t
",
);
}
#[test]
fn item_map_using_self() {
let (item_map, module_id) = item_map(
@ -141,6 +170,59 @@ fn item_map_across_crates() {
#[test]
fn typing_inside_a_function_should_not_invalidate_item_map() {
let (mut db, pos) = MockDatabase::with_position(
"
//- /lib.rs
mod foo;
use crate::foo::bar::Baz;
//- /foo/mod.rs
pub mod bar;
//- /foo/bar.rs
<|>
salsa::query_group! {
trait Baz {
fn foo() -> i32 { 1 + 1 }
}
}
",
);
let source_root = db.file_source_root(pos.file_id);
{
let events = db.log_executed(|| {
db.item_map(source_root).unwrap();
});
assert!(format!("{:?}", events).contains("item_map"))
}
let new_text = "
salsa::query_group! {
trait Baz {
fn foo() -> i32 { 92 }
}
}
"
.to_string();
db.query_mut(ra_db::FileTextQuery)
.set(pos.file_id, Arc::new(new_text));
{
let events = db.log_executed(|| {
db.item_map(source_root).unwrap();
});
assert!(
!format!("{:?}", events).contains("item_map"),
"{:#?}",
events
)
}
}
#[test]
fn typing_inside_a_function_inside_a_macro_should_not_invalidate_item_map() {
let (mut db, pos) = MockDatabase::with_position(
"
//- /lib.rs
@ -183,7 +265,7 @@ fn typing_inside_a_function_should_not_invalidate_item_map() {
db.item_map(source_root).unwrap();
});
assert!(
!format!("{:?}", events).contains("_item_map"),
!format!("{:?}", events).contains("item_map"),
"{:#?}",
events
)

View file

@ -8,10 +8,11 @@ use ra_syntax::{
AstNode, SyntaxNode,
ast::{self, NameOwner, ModuleItemOwner}
};
use ra_db::{SourceRootId, FileId, Cancelable,};
use ra_db::{SourceRootId, Cancelable,};
use crate::{
SourceFileItems, SourceItemId, DefKind, Function, DefId, Name, AsName,
SourceFileItems, SourceItemId, DefKind, Function, DefId, Name, AsName, HirFileId,
MacroCallLoc,
db::HirDatabase,
function::FnScopes,
module::{
@ -47,25 +48,17 @@ pub(super) fn enum_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Arc<
Ok(Arc::new(EnumData::new(enum_def.borrowed())))
}
pub(super) fn file_items(db: &impl HirDatabase, file_id: FileId) -> Arc<SourceFileItems> {
let mut res = SourceFileItems::new(file_id);
let source_file = db.source_file(file_id);
pub(super) fn file_items(db: &impl HirDatabase, file_id: HirFileId) -> Arc<SourceFileItems> {
let source_file = db.hir_source_file(file_id);
let source_file = source_file.borrowed();
source_file
.syntax()
.descendants()
.filter_map(ast::ModuleItem::cast)
.map(|it| it.syntax().owned())
.for_each(|it| {
res.alloc(it);
});
let res = SourceFileItems::new(file_id, source_file);
Arc::new(res)
}
pub(super) fn file_item(db: &impl HirDatabase, source_item_id: SourceItemId) -> SyntaxNode {
match source_item_id.item_id {
Some(id) => db.file_items(source_item_id.file_id)[id].clone(),
None => db.source_file(source_item_id.file_id).syntax().owned(),
None => db.hir_source_file(source_item_id.file_id).syntax().owned(),
}
}
@ -87,7 +80,7 @@ pub(crate) fn submodules(
fn collect_submodules<'a>(
db: &impl HirDatabase,
file_id: FileId,
file_id: HirFileId,
root: impl ast::ModuleItemOwner<'a>,
) -> Vec<Submodule> {
modules(root)
@ -119,24 +112,48 @@ pub(crate) fn modules<'a>(
pub(super) fn input_module_items(
db: &impl HirDatabase,
source_root: SourceRootId,
source_root_id: SourceRootId,
module_id: ModuleId,
) -> Cancelable<Arc<InputModuleItems>> {
let module_tree = db.module_tree(source_root)?;
let module_tree = db.module_tree(source_root_id)?;
let source = module_id.source(&module_tree);
let file_items = db.file_items(source.file_id());
let res = match source.resolve(db) {
ModuleSourceNode::SourceFile(it) => {
let items = it.borrowed().items();
InputModuleItems::new(&file_items, items)
let file_id = source.file_id();
let file_items = db.file_items(file_id);
let fill = |acc: &mut InputModuleItems, items: &mut Iterator<Item = ast::ItemOrMacro>| {
for item in items {
match item {
ast::ItemOrMacro::Item(it) => {
acc.add_item(file_id, &file_items, it);
}
ast::ItemOrMacro::Macro(macro_call) => {
let item_id = file_items.id_of_unchecked(macro_call.syntax());
let loc = MacroCallLoc {
source_root_id,
module_id,
source_item_id: SourceItemId {
file_id,
item_id: Some(item_id),
},
};
let id = loc.id(db);
let file_id = HirFileId::from(id);
let file_items = db.file_items(file_id);
//FIXME: expand recursively
for item in db.hir_source_file(file_id).borrowed().items() {
acc.add_item(file_id, &file_items, item);
}
}
}
}
};
let mut res = InputModuleItems::default();
match source.resolve(db) {
ModuleSourceNode::SourceFile(it) => fill(&mut res, &mut it.borrowed().items_with_macros()),
ModuleSourceNode::Module(it) => {
let items = it
.borrowed()
.item_list()
.into_iter()
.flat_map(|it| it.items());
InputModuleItems::new(&file_items, items)
if let Some(item_list) = it.borrowed().item_list() {
fill(&mut res, &mut item_list.items_with_macros())
}
}
};
Ok(Arc::new(res))

View file

@ -20,7 +20,7 @@ use crate::{
/// 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);
let module_source = ModuleSource::new_file(file_id.into());
module_from_source(db, module_source)
}
@ -50,8 +50,8 @@ pub fn module_from_position(
) -> 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),
Some(m) if !m.has_semi() => ModuleSource::new_inline(db, position.file_id.into(), m),
_ => ModuleSource::new_file(position.file_id.into()),
};
module_from_source(db, module_source)
}
@ -67,9 +67,9 @@ pub fn module_from_child_node(
.filter_map(ast::Module::cast)
.find(|it| !it.has_semi())
{
ModuleSource::new_inline(db, file_id, m)
ModuleSource::new_inline(db, file_id.into(), m)
} else {
ModuleSource::new_file(file_id)
ModuleSource::new_file(file_id.into())
};
module_from_source(db, module_source)
}
@ -78,7 +78,7 @@ 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 source_root_id = db.file_source_root(module_source.file_id().as_original_file());
let module_tree = db.module_tree(source_root_id)?;
let m = module_tree
.modules_with_sources()

View file

@ -48,10 +48,40 @@ pub trait FnDefOwner<'a>: AstNode<'a> {
}
}
// ModuleItem
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItemOrMacro<'a> {
Item(ModuleItem<'a>),
Macro(MacroCall<'a>),
}
impl<'a> AstNode<'a> for ItemOrMacro<'a> {
fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
let res = if let Some(item) = ModuleItem::cast(syntax) {
ItemOrMacro::Item(item)
} else if let Some(macro_call) = MacroCall::cast(syntax) {
ItemOrMacro::Macro(macro_call)
} else {
return None;
};
Some(res)
}
fn syntax(self) -> SyntaxNodeRef<'a> {
match self {
ItemOrMacro::Item(it) => it.syntax(),
ItemOrMacro::Macro(it) => it.syntax(),
}
}
}
pub trait ModuleItemOwner<'a>: AstNode<'a> {
fn items(self) -> AstChildren<'a, ModuleItem<'a>> {
children(self)
}
fn items_with_macros(self) -> AstChildren<'a, ItemOrMacro<'a>> {
children(self)
}
}
pub trait TypeParamsOwner<'a>: AstNode<'a> {

View file

@ -51,7 +51,7 @@ use ra_text_edit::AtomTextEdit;
use crate::yellow::GreenNode;
/// `SourceFileNode` represents a parse tree for a single Rust file.
pub use crate::ast::SourceFileNode;
pub use crate::ast::{SourceFile, SourceFileNode};
impl SourceFileNode {
fn new(green: GreenNode, errors: Vec<SyntaxError>) -> SourceFileNode {