From 18f6a995d0fc1f45099f3cc810a5d55d5401b41b Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Thu, 5 Dec 2019 15:10:33 +0100 Subject: Add expansion infrastructure for derive macros --- crates/ra_hir_expand/src/ast_id_map.rs | 12 ++++- crates/ra_hir_expand/src/builtin_derive.rs | 64 +++++++++++++++++++++++++ crates/ra_hir_expand/src/builtin_macro.rs | 37 +++++++-------- crates/ra_hir_expand/src/db.rs | 19 +++++--- crates/ra_hir_expand/src/hygiene.rs | 3 +- crates/ra_hir_expand/src/lib.rs | 76 ++++++++++++++++++++---------- crates/ra_hir_expand/src/name.rs | 10 ++++ 7 files changed, 170 insertions(+), 51 deletions(-) create mode 100644 crates/ra_hir_expand/src/builtin_derive.rs (limited to 'crates/ra_hir_expand') diff --git a/crates/ra_hir_expand/src/ast_id_map.rs b/crates/ra_hir_expand/src/ast_id_map.rs index cb464c3ff..a764bdf24 100644 --- a/crates/ra_hir_expand/src/ast_id_map.rs +++ b/crates/ra_hir_expand/src/ast_id_map.rs @@ -39,6 +39,16 @@ impl Hash for FileAstId { } } +impl FileAstId { + // Can't make this a From implementation because of coherence + pub fn upcast(self) -> FileAstId + where + M: From, + { + FileAstId { raw: self.raw, _ty: PhantomData } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct ErasedFileAstId(RawId); impl_arena_id!(ErasedFileAstId); @@ -53,7 +63,7 @@ impl AstIdMap { pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap { assert!(node.parent().is_none()); let mut res = AstIdMap { arena: Arena::default() }; - // By walking the tree in bread-first order we make sure that parents + // By walking the tree in breadth-first order we make sure that parents // get lower ids then children. That is, adding a new child does not // change parent's id. This means that, say, adding a new function to a // trait does not change ids of top-level items, which helps caching. diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs new file mode 100644 index 000000000..0a70c63c0 --- /dev/null +++ b/crates/ra_hir_expand/src/builtin_derive.rs @@ -0,0 +1,64 @@ +//! Builtin derives. +use crate::db::AstDatabase; +use crate::{name, MacroCallId, MacroDefId, MacroDefKind}; + +use crate::quote; + +macro_rules! register_builtin { + ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinDeriveExpander { + $($kind),* + } + + impl BuiltinDeriveExpander { + pub fn expand( + &self, + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, + ) -> Result { + let expander = match *self { + $( BuiltinDeriveExpander::$kind => $expand, )* + }; + expander(db, id, tt) + } + } + + pub fn find_builtin_derive(ident: &name::Name) -> Option { + let kind = match ident { + $( id if id == &name::$name => BuiltinDeriveExpander::$kind, )* + _ => return None, + }; + + Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind) }) + } + }; +} + +register_builtin! { + (COPY_TRAIT, Copy) => copy_expand, + (CLONE_TRAIT, Clone) => clone_expand +} + +fn copy_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> Result { + let expanded = quote! { + impl Copy for Foo {} + }; + Ok(expanded) +} + +fn clone_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> Result { + let expanded = quote! { + impl Clone for Foo {} + }; + Ok(expanded) +} diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index d370dfb34..35f99b2bc 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs @@ -39,7 +39,7 @@ macro_rules! register_builtin { _ => return None, }; - Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) }) + Some(MacroDefId { krate: Some(krate), ast_id: Some(ast_id), kind: MacroDefKind::BuiltIn(kind) }) } }; } @@ -82,10 +82,9 @@ fn line_expand( _tt: &tt::Subtree, ) -> Result { let loc = db.lookup_intern_macro(id); - let macro_call = loc.ast_id.to_node(db); - let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; - let arg_start = arg.syntax().text_range().start(); + let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; + let arg_start = arg.text_range().start(); let file = id.as_file(MacroFileKind::Expr); let line_num = to_line_number(db, file, arg_start); @@ -103,11 +102,10 @@ fn stringify_expand( _tt: &tt::Subtree, ) -> Result { let loc = db.lookup_intern_macro(id); - let macro_call = loc.ast_id.to_node(db); let macro_content = { - let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; - let macro_args = arg.syntax().clone(); + let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; + let macro_args = arg.clone(); let text = macro_args.text(); let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); text.slice(without_parens).to_string() @@ -148,7 +146,10 @@ fn column_expand( _tt: &tt::Subtree, ) -> Result { let loc = db.lookup_intern_macro(id); - let macro_call = loc.ast_id.to_node(db); + let macro_call = match loc.kind { + crate::MacroCallKind::FnLike(ast_id) => ast_id.to_node(db), + _ => panic!("column macro called as attr"), + }; let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; let col_start = macro_call.syntax().text_range().start(); @@ -164,15 +165,10 @@ fn column_expand( } fn file_expand( - db: &dyn AstDatabase, - id: MacroCallId, + _db: &dyn AstDatabase, + _id: MacroCallId, _tt: &tt::Subtree, ) -> Result { - let loc = db.lookup_intern_macro(id); - let macro_call = loc.ast_id.to_node(db); - - let _ = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; - // FIXME: RA purposefully lacks knowledge of absolute file names // so just return "". let file_name = ""; @@ -207,7 +203,7 @@ fn compile_error_expand( #[cfg(test)] mod tests { use super::*; - use crate::{test_db::TestDB, MacroCallLoc}; + use crate::{test_db::TestDB, MacroCallKind, MacroCallLoc}; use ra_db::{fixture::WithFixture, SourceDatabase}; fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String { @@ -220,14 +216,17 @@ mod tests { // the first one should be a macro_rules let def = MacroDefId { - krate: CrateId(0), - ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0])), + krate: Some(CrateId(0)), + ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), kind: MacroDefKind::BuiltIn(expander), }; let loc = MacroCallLoc { def, - ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[1])), + kind: MacroCallKind::FnLike(AstId::new( + file_id.into(), + ast_id_map.ast_id(¯o_calls[1]), + )), }; let id = db.intern_macro(loc); diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index 8e46fa177..99dabf3fb 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs @@ -9,14 +9,15 @@ use ra_prof::profile; use ra_syntax::{AstNode, Parse, SyntaxNode}; use crate::{ - ast_id_map::AstIdMap, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, - MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind, + ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, + MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind, }; #[derive(Debug, Clone, Eq, PartialEq)] pub enum TokenExpander { MacroRules(mbe::MacroRules), Builtin(BuiltinFnLikeExpander), + BuiltinDerive(BuiltinDeriveExpander), } impl TokenExpander { @@ -29,6 +30,7 @@ impl TokenExpander { match self { TokenExpander::MacroRules(it) => it.expand(tt), TokenExpander::Builtin(it) => it.expand(db, id, tt), + TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt), } } @@ -36,6 +38,7 @@ impl TokenExpander { match self { TokenExpander::MacroRules(it) => it.map_id_down(id), TokenExpander::Builtin(..) => id, + TokenExpander::BuiltinDerive(..) => id, } } @@ -43,6 +46,7 @@ impl TokenExpander { match self { TokenExpander::MacroRules(it) => it.map_id_up(id), TokenExpander::Builtin(..) => (id, mbe::Origin::Def), + TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Def), } } } @@ -76,7 +80,7 @@ pub(crate) fn macro_def( ) -> Option> { match id.kind { MacroDefKind::Declarative => { - let macro_call = id.ast_id.to_node(db); + let macro_call = id.ast_id?.to_node(db); let arg = macro_call.token_tree()?; let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| { log::warn!("fail on macro_def to token tree: {:#?}", arg); @@ -91,6 +95,10 @@ pub(crate) fn macro_def( MacroDefKind::BuiltIn(expander) => { Some(Arc::new((TokenExpander::Builtin(expander.clone()), mbe::TokenMap::default()))) } + MacroDefKind::BuiltInDerive(expander) => Some(Arc::new(( + TokenExpander::BuiltinDerive(expander.clone()), + mbe::TokenMap::default(), + ))), } } @@ -99,9 +107,8 @@ pub(crate) fn macro_arg( id: MacroCallId, ) -> Option> { let loc = db.lookup_intern_macro(id); - let macro_call = loc.ast_id.to_node(db); - let arg = macro_call.token_tree()?; - let (tt, tmap) = mbe::ast_to_token_tree(&arg)?; + let arg = loc.kind.arg(db)?; + let (tt, tmap) = mbe::syntax_node_to_token_tree(&arg)?; Some(Arc::new((tt, tmap))) } diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs index 64c8b06c6..2e8a533f7 100644 --- a/crates/ra_hir_expand/src/hygiene.rs +++ b/crates/ra_hir_expand/src/hygiene.rs @@ -25,8 +25,9 @@ impl Hygiene { HirFileIdRepr::MacroFile(macro_file) => { let loc = db.lookup_intern_macro(macro_file.macro_call_id); match loc.def.kind { - MacroDefKind::Declarative => Some(loc.def.krate), + MacroDefKind::Declarative => loc.def.krate, MacroDefKind::BuiltIn(_) => None, + MacroDefKind::BuiltInDerive(_) => None, } } }; diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 3be9bdf86..59c69b91b 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs @@ -9,6 +9,7 @@ pub mod ast_id_map; pub mod name; pub mod hygiene; pub mod diagnostics; +pub mod builtin_derive; pub mod builtin_macro; pub mod quote; @@ -23,6 +24,7 @@ use ra_syntax::{ }; use crate::ast_id_map::FileAstId; +use crate::builtin_derive::BuiltinDeriveExpander; use crate::builtin_macro::BuiltinFnLikeExpander; #[cfg(test)] @@ -69,7 +71,7 @@ impl HirFileId { HirFileIdRepr::FileId(file_id) => file_id, HirFileIdRepr::MacroFile(macro_file) => { let loc = db.lookup_intern_macro(macro_file.macro_call_id); - loc.ast_id.file_id.original_file(db) + loc.kind.file_id().original_file(db) } } } @@ -81,8 +83,8 @@ impl HirFileId { HirFileIdRepr::MacroFile(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); - let arg_tt = loc.ast_id.to_node(db).token_tree()?; - let def_tt = loc.def.ast_id.to_node(db).token_tree()?; + let arg_tt = loc.kind.arg(db)?; + let def_tt = loc.def.ast_id?.to_node(db).token_tree()?; let macro_def = db.macro_def(loc.def)?; let (parse, exp_map) = db.parse_macro(macro_file)?; @@ -90,8 +92,8 @@ impl HirFileId { Some(ExpansionInfo { expanded: InFile::new(self, parse.syntax_node()), - arg: InFile::new(loc.ast_id.file_id, arg_tt), - def: InFile::new(loc.ast_id.file_id, def_tt), + arg: InFile::new(loc.kind.file_id(), arg_tt), + def: InFile::new(loc.def.ast_id?.file_id, def_tt), macro_arg, macro_def, exp_map, @@ -129,18 +131,20 @@ impl salsa::InternKey for MacroCallId { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroDefId { - pub krate: CrateId, - pub ast_id: AstId, + // FIXME: krate and ast_id are currently optional because we don't have a + // definition location for built-in derives. There is one, though: the + // standard library defines them. The problem is that it uses the new + // `macro` syntax for this, which we don't support yet. As soon as we do + // (which will probably require touching this code), we can instead use + // that (and also remove the hacks for resolving built-in derives). + pub krate: Option, + pub ast_id: Option>, pub kind: MacroDefKind, } impl MacroDefId { - pub fn as_call_id( - self, - db: &dyn db::AstDatabase, - ast_id: AstId, - ) -> MacroCallId { - db.intern_macro(MacroCallLoc { def: self, ast_id }) + pub fn as_call_id(self, db: &dyn db::AstDatabase, kind: MacroCallKind) -> MacroCallId { + db.intern_macro(MacroCallLoc { def: self, kind }) } } @@ -148,12 +152,38 @@ impl MacroDefId { pub enum MacroDefKind { Declarative, BuiltIn(BuiltinFnLikeExpander), + // FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander + BuiltInDerive(BuiltinDeriveExpander), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroCallLoc { pub(crate) def: MacroDefId, - pub(crate) ast_id: AstId, + pub(crate) kind: MacroCallKind, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum MacroCallKind { + FnLike(AstId), + Attr(AstId), +} + +impl MacroCallKind { + pub fn file_id(&self) -> HirFileId { + match self { + MacroCallKind::FnLike(ast_id) => ast_id.file_id, + MacroCallKind::Attr(ast_id) => ast_id.file_id, + } + } + + pub fn arg(&self, db: &dyn db::AstDatabase) -> Option { + match self { + MacroCallKind::FnLike(ast_id) => { + Some(ast_id.to_node(db).token_tree()?.syntax().clone()) + } + MacroCallKind::Attr(ast_id) => Some(ast_id.to_node(db).syntax().clone()), + } + } } impl MacroCallId { @@ -167,7 +197,7 @@ impl MacroCallId { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExpansionInfo { expanded: InFile, - arg: InFile, + arg: InFile, def: InFile, macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>, @@ -178,8 +208,7 @@ pub struct ExpansionInfo { impl ExpansionInfo { pub fn map_token_down(&self, token: InFile<&SyntaxToken>) -> Option> { assert_eq!(token.file_id, self.arg.file_id); - let range = - token.value.text_range().checked_sub(self.arg.value.syntax().text_range().start())?; + let range = token.value.text_range().checked_sub(self.arg.value.text_range().start())?; let token_id = self.macro_arg.1.token_by_range(range)?; let token_id = self.macro_def.0.map_id_down(token_id); @@ -195,16 +224,15 @@ impl ExpansionInfo { let (token_id, origin) = self.macro_def.0.map_id_up(token_id); let (token_map, tt) = match origin { - mbe::Origin::Call => (&self.macro_arg.1, &self.arg), - mbe::Origin::Def => (&self.macro_def.1, &self.def), + mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()), + mbe::Origin::Def => { + (&self.macro_def.1, self.def.as_ref().map(|tt| tt.syntax().clone())) + } }; let range = token_map.range_by_token(token_id)?; - let token = algo::find_covering_element( - tt.value.syntax(), - range + tt.value.syntax().text_range().start(), - ) - .into_token()?; + let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start()) + .into_token()?; Some(tt.with_value(token)) } } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index 05ba37070..86709b5cf 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -83,6 +83,12 @@ impl AsName for ast::Name { } } +impl AsName for tt::Ident { + fn as_name(&self) -> Name { + Name::resolve(&self.text) + } +} + impl AsName for ast::FieldKind { fn as_name(&self) -> Name { match self { @@ -153,3 +159,7 @@ pub const COLUMN_MACRO: Name = Name::new_inline_ascii(6, b"column"); pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(13, b"compile_error"); pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line"); pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(9, b"stringify"); + +// Builtin derives +pub const COPY_TRAIT: Name = Name::new_inline_ascii(4, b"Copy"); +pub const CLONE_TRAIT: Name = Name::new_inline_ascii(5, b"Clone"); -- cgit v1.2.3