remove old macro support

This commit is contained in:
Aleksey Kladov 2019-03-16 19:40:41 +03:00
parent ee3cf6172b
commit 6955e392f8
8 changed files with 94 additions and 260 deletions

View file

@ -4,12 +4,10 @@ use ra_syntax::{SyntaxNode, TreeArc, SourceFile};
use ra_db::{SourceDatabase, salsa, FileId};
use crate::{
MacroCallId, HirFileId,
SourceFileItems, SourceItemId, Crate, Module, HirInterner,
HirFileId, SourceFileItems, SourceItemId, Crate, Module, HirInterner,
Function, FnSignature, ExprScopes, TypeAlias,
Struct, Enum, StructField,
Const, ConstSignature, Static,
macros::MacroExpansion,
nameres::{Namespace, ImportSourceMap, RawItems, CrateDefMap},
ty::{InferenceResult, Ty, method_resolution::CrateImplBlocks, TypableDef, CallableDef, FnSig},
adt::{StructData, EnumData},
@ -23,9 +21,6 @@ pub trait PersistentHirDatabase: SourceDatabase + AsRef<HirInterner> {
#[salsa::invoke(HirFileId::hir_parse)]
fn hir_parse(&self, file_id: HirFileId) -> TreeArc<SourceFile>;
#[salsa::invoke(crate::macros::expand_macro_invocation)]
fn expand_macro_invocation(&self, invoc: MacroCallId) -> Option<Arc<MacroExpansion>>;
#[salsa::invoke(crate::adt::StructData::struct_data_query)]
fn struct_data(&self, s: Struct) -> Arc<StructData>;

View file

@ -89,17 +89,31 @@ impl HirFileId {
) -> TreeArc<SourceFile> {
match file_id.0 {
HirFileIdRepr::File(file_id) => db.parse(file_id),
HirFileIdRepr::Macro(m) => {
if let Some(exp) = db.expand_macro_invocation(m) {
return exp.file();
}
HirFileIdRepr::Macro(macro_call_id) => {
// returning an empty string looks fishy...
SourceFile::parse("")
parse_macro(db, macro_call_id).unwrap_or_else(|| SourceFile::parse(""))
}
}
}
}
fn parse_macro(
db: &impl PersistentHirDatabase,
macro_call_id: MacroCallId,
) -> Option<TreeArc<SourceFile>> {
let loc = macro_call_id.loc(db);
let syntax = db.file_item(loc.source_item_id);
let macro_call = ast::MacroCall::cast(&syntax).unwrap();
let (macro_arg, _) = macro_call.token_tree().and_then(mbe::ast_to_token_tree)?;
let def_map = db.crate_def_map(loc.module.krate);
let (krate, macro_id) = def_map.resolve_macro(macro_call_id)?;
let def_map = db.crate_def_map(krate);
let macro_rules = &def_map[macro_id];
let tt = macro_rules.expand(&macro_arg).ok()?;
Some(mbe::token_tree_to_ast_item_list(&tt))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum HirFileIdRepr {
File(FileId),
@ -373,7 +387,6 @@ impl SourceFileItems {
impl std::ops::Index<SourceFileItemId> for SourceFileItems {
type Output = SyntaxNodePtr;
fn index(&self, idx: SourceFileItemId) -> &SyntaxNodePtr {
eprintln!("invalid SourceFileItemId({:?}) for file({:?})", idx, self.file_id);
&self.arena[idx]
}
}

View file

@ -24,7 +24,6 @@ mod path;
pub mod source_binder;
mod ids;
mod macros;
mod name;
mod nameres;
mod adt;
@ -53,7 +52,6 @@ pub use self::{
path::{Path, PathKind},
name::Name,
ids::{HirFileId, MacroCallId, MacroCallLoc, HirInterner},
macros::{MacroDef, MacroInput, MacroExpansion},
nameres::{PerNs, Namespace},
ty::{Ty, Substs, display::HirDisplay},
impl_block::{ImplBlock, ImplItem},

View file

@ -1,135 +0,0 @@
/// 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 the file-management question is resolved, all that is left is a
/// token-tree-to-token-tree transformation plus hygiene. We don't have either of
/// those yet, so all macros are string based at the moment!
use std::sync::Arc;
use ra_syntax::{
TextRange, TextUnit, SourceFile, AstNode, SyntaxNode, TreeArc, SyntaxNodePtr,
ast,
};
use crate::{MacroCallId, PersistentHirDatabase};
// Hard-coded defs for now :-(
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacroDef {
Vec,
}
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() == "vec" {
MacroDef::Vec
} 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::Vec => self.expand_vec(input),
}
}
fn expand_vec(self, input: MacroInput) -> Option<MacroExpansion> {
let text = format!(r"fn dummy() {{ {}; }}", input.text);
let file = SourceFile::parse(&text);
let array_expr = file.syntax().descendants().find_map(ast::ArrayExpr::cast)?;
let ptr = SyntaxNodePtr::new(array_expr.syntax());
let src_range = TextRange::offset_len(0.into(), TextUnit::of_str(&input.text));
let ranges_map = vec![(src_range, array_expr.syntax().range())];
let res = MacroExpansion { text, ranges_map, ptr };
Some(res)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
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: SyntaxNodePtr,
}
impl MacroExpansion {
// FIXME: does not really make sense, macro expansion is not necessary a
// whole file. See `MacroExpansion::ptr` as well.
pub(crate) fn file(&self) -> TreeArc<SourceFile> {
SourceFile::parse(&self.text)
}
pub fn syntax(&self) -> TreeArc<SyntaxNode> {
self.ptr.to_node(&self.file()).to_owned()
}
/// 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 PersistentHirDatabase,
invoc: MacroCallId,
) -> Option<Arc<MacroExpansion>> {
let loc = invoc.loc(db);
let syntax = db.file_item(loc.source_item_id);
let macro_call = ast::MacroCall::cast(&syntax).unwrap();
let (def, input) = MacroDef::from_call(macro_call)?;
def.expand(input).map(Arc::new)
}

View file

@ -57,7 +57,7 @@ use test_utils::tested_by;
use crate::{
ModuleDef, Name, Crate, Module, Problem,
PersistentHirDatabase, Path, PathKind, HirFileId,
ids::{SourceItemId, SourceFileItemId},
ids::{SourceItemId, SourceFileItemId, MacroCallId},
};
pub(crate) use self::raw::{RawItems, ImportId, ImportSourceMap};
@ -76,7 +76,9 @@ pub struct CrateDefMap {
extern_prelude: FxHashMap<Name, ModuleDef>,
root: CrateModuleId,
modules: Arena<CrateModuleId, ModuleData>,
public_macros: FxHashMap<Name, mbe::MacroRules>,
macros: Arena<CrateMacroId, mbe::MacroRules>,
public_macros: FxHashMap<Name, CrateMacroId>,
macro_resolutions: FxHashMap<MacroCallId, (Crate, CrateMacroId)>,
problems: CrateDefMapProblems,
}
@ -87,9 +89,21 @@ impl std::ops::Index<CrateModuleId> for CrateDefMap {
}
}
impl std::ops::Index<CrateMacroId> for CrateDefMap {
type Output = mbe::MacroRules;
fn index(&self, id: CrateMacroId) -> &mbe::MacroRules {
&self.macros[id]
}
}
/// An ID of a macro, **local** to a specific crate
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct CrateMacroId(RawId);
impl_arena_id!(CrateMacroId);
/// An ID of a module, **local** to a specific crate
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct CrateModuleId(RawId);
pub(crate) struct CrateModuleId(RawId);
impl_arena_id!(CrateModuleId);
#[derive(Default, Debug, PartialEq, Eq)]
@ -192,7 +206,9 @@ impl CrateDefMap {
prelude: None,
root,
modules,
macros: Arena::default(),
public_macros: FxHashMap::default(),
macro_resolutions: FxHashMap::default(),
problems: CrateDefMapProblems::default(),
}
};
@ -221,6 +237,13 @@ impl CrateDefMap {
&self.extern_prelude
}
pub(crate) fn resolve_macro(
&self,
macro_call_id: MacroCallId,
) -> Option<(Crate, CrateMacroId)> {
self.macro_resolutions.get(&macro_call_id).map(|&it| it)
}
pub(crate) fn find_module_by_source(
&self,
file_id: HirFileId,

View file

@ -6,13 +6,13 @@ use ra_db::FileId;
use crate::{
Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias,
PersistentHirDatabase, HirFileId, Name, Path, Problem,
PersistentHirDatabase, HirFileId, Name, Path, Problem, Crate,
KnownName,
nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode, raw},
ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId},
};
use super::{CrateDefMap, CrateModuleId, ModuleData};
use super::{CrateDefMap, CrateModuleId, ModuleData, CrateMacroId};
pub(super) fn collect_defs(
db: &impl PersistentHirDatabase,
@ -52,7 +52,7 @@ struct DefCollector<DB> {
glob_imports: FxHashMap<CrateModuleId, Vec<(CrateModuleId, raw::ImportId)>>,
unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>,
unexpanded_macros: Vec<(CrateModuleId, MacroCallId, Path, tt::Subtree)>,
global_macro_scope: FxHashMap<Name, mbe::MacroRules>,
global_macro_scope: FxHashMap<Name, CrateMacroId>,
}
impl<'a, DB> DefCollector<&'a DB>
@ -95,10 +95,11 @@ where
fn define_macro(&mut self, name: Name, tt: &tt::Subtree, export: bool) {
if let Ok(rules) = mbe::MacroRules::parse(tt) {
let macro_id = self.def_map.macros.alloc(rules);
if export {
self.def_map.public_macros.insert(name.clone(), rules.clone());
self.def_map.public_macros.insert(name.clone(), macro_id);
}
self.global_macro_scope.insert(name, rules);
self.global_macro_scope.insert(name, macro_id);
}
}
@ -295,6 +296,7 @@ where
fn resolve_macros(&mut self) -> ReachedFixedPoint {
let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new());
let mut resolved = Vec::new();
let mut res = ReachedFixedPoint::Yes;
macros.retain(|(module_id, call_id, path, tt)| {
if path.segments.len() != 2 {
return true;
@ -308,19 +310,16 @@ where
Some(it) => it,
_ => return true,
};
res = ReachedFixedPoint::No;
let def_map = self.db.crate_def_map(krate);
let rules = def_map.public_macros.get(&path.segments[1].name).cloned();
resolved.push((*module_id, *call_id, rules, tt.clone()));
if let Some(macro_id) = def_map.public_macros.get(&path.segments[1].name).cloned() {
resolved.push((*module_id, *call_id, (krate, macro_id), tt.clone()));
}
false
});
let res = if resolved.is_empty() { ReachedFixedPoint::Yes } else { ReachedFixedPoint::No };
for (module_id, macro_call_id, rules, arg) in resolved {
if let Some(rules) = rules {
if let Ok(tt) = rules.expand(&arg) {
self.collect_macro_expansion(module_id, macro_call_id, tt);
}
}
for (module_id, macro_call_id, macro_def_id, arg) in resolved {
self.collect_macro_expansion(module_id, macro_call_id, macro_def_id, arg);
}
res
}
@ -329,20 +328,32 @@ where
&mut self,
module_id: CrateModuleId,
macro_call_id: MacroCallId,
expansion: tt::Subtree,
macro_def_id: (Crate, CrateMacroId),
macro_arg: tt::Subtree,
) {
// XXX: this **does not** go through a database, because we can't
// identify macro_call without adding the whole state of name resolution
// as a parameter to the query.
//
// So, we run the queries "manually" and we must ensure that
// `db.hir_parse(macro_call_id)` returns the same source_file.
let file_id: HirFileId = macro_call_id.into();
let source_file = mbe::token_tree_to_ast_item_list(&expansion);
let (macro_krate, macro_id) = macro_def_id;
let dm;
let rules = if macro_krate == self.def_map.krate {
&self.def_map[macro_id]
} else {
dm = self.db.crate_def_map(macro_krate);
&dm[macro_id]
};
if let Ok(expansion) = rules.expand(&macro_arg) {
self.def_map.macro_resolutions.insert(macro_call_id, macro_def_id);
// XXX: this **does not** go through a database, because we can't
// identify macro_call without adding the whole state of name resolution
// as a parameter to the query.
//
// So, we run the queries "manually" and we must ensure that
// `db.hir_parse(macro_call_id)` returns the same source_file.
let file_id: HirFileId = macro_call_id.into();
let source_file = mbe::token_tree_to_ast_item_list(&expansion);
let raw_items = raw::RawItems::from_source_file(&source_file, file_id);
ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items }
.collect(raw_items.items())
let raw_items = raw::RawItems::from_source_file(&source_file, file_id);
ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items }
.collect(raw_items.items())
}
}
fn finish(self) -> CrateDefMap {
@ -486,12 +497,15 @@ where
// Case 2: try to expand macro_rules from this crate, triggering
// recursive item collection.
if let Some(rules) =
if let Some(&macro_id) =
mac.path.as_ident().and_then(|name| self.def_collector.global_macro_scope.get(name))
{
if let Ok(tt) = rules.expand(&mac.arg) {
self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, tt);
}
self.def_collector.collect_macro_expansion(
self.module_id,
macro_call_id,
(self.def_collector.def_map.krate, macro_id),
mac.arg.clone(),
);
return;
}

View file

@ -1,55 +1,13 @@
use ra_db::SourceDatabase;
use ra_syntax::{
SyntaxNode, AstNode, SourceFile,
ast, algo::find_covering_node,
};
use ra_syntax::AstNode;
use crate::{
TextRange, FileRange,
db::RootDatabase,
};
// FIXME: restore macro support
pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
let source_file = db.parse(frange.file_id);
if let Some(range) = extend_selection_in_macro(db, &source_file, frange) {
return range;
}
ra_ide_api_light::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range)
}
fn extend_selection_in_macro(
_db: &RootDatabase,
source_file: &SourceFile,
frange: FileRange,
) -> Option<TextRange> {
let macro_call = find_macro_call(source_file.syntax(), frange.range)?;
let (off, exp) = hir::MacroDef::ast_expand(macro_call)?;
let dst_range = exp.map_range_forward(frange.range - off)?;
let dst_range = ra_ide_api_light::extend_selection(&exp.syntax(), dst_range)?;
let src_range = exp.map_range_back(dst_range)? + off;
Some(src_range)
}
fn find_macro_call(node: &SyntaxNode, range: TextRange) -> Option<&ast::MacroCall> {
find_covering_node(node, range).ancestors().find_map(ast::MacroCall::cast)
}
#[cfg(test)]
mod tests {
use ra_syntax::TextRange;
use crate::mock_analysis::single_file_with_range;
#[test]
fn extend_selection_inside_macros() {
let (analysis, frange) = single_file_with_range(
"
fn main() {
vec![foo(|x| <|>x<|>)];
}
",
);
let r = analysis.extend_selection(frange).unwrap();
assert_eq!(r, TextRange::from_to(50.into(), 55.into()));
}
}

View file

@ -1,4 +1,4 @@
use ra_syntax::{ast, AstNode,};
use ra_syntax::AstNode;
use ra_db::SourceDatabase;
use crate::{
@ -8,37 +8,5 @@ use crate::{
pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> {
let source_file = db.parse(file_id);
let mut res = ra_ide_api_light::highlight(source_file.syntax());
for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) {
if let Some((off, exp)) = hir::MacroDef::ast_expand(macro_call) {
let mapped_ranges =
ra_ide_api_light::highlight(&exp.syntax()).into_iter().filter_map(|r| {
let mapped_range = exp.map_range_back(r.range)?;
let res = HighlightedRange { range: mapped_range + off, tag: r.tag };
Some(res)
});
res.extend(mapped_ranges);
}
}
res
}
#[cfg(test)]
mod tests {
use crate::mock_analysis::single_file;
use insta::assert_debug_snapshot_matches;
#[test]
fn highlights_code_inside_macros() {
let (analysis, file_id) = single_file(
"
fn main() {
vec![{ let x = 92; x}];
}
",
);
let highlights = analysis.highlight(file_id).unwrap();
assert_debug_snapshot_matches!("highlights_code_inside_macros", &highlights);
}
ra_ide_api_light::highlight(source_file.syntax())
}