Skip to content

[WIP] Expansion of #[derive(Trait)] macro attributes #1635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2ae667f
Collect derive attributes as a special kind of macro call in raw items
eupn Aug 21, 2019
85ce56a
rustfmt
eupn Aug 21, 2019
4d377eb
Collect derive attributes and modify MacroData to hold derive attribute
eupn Aug 22, 2019
75ab5da
Make AstId public globally
eupn Aug 22, 2019
eacaeb1
Extend MacroCallId and MacroCallLoc to be aware of Derive macros
eupn Aug 22, 2019
b4dd741
rustfmt
eupn Aug 22, 2019
dfdb5f0
Add target to attribute macro and generalize attribute macros
eupn Aug 31, 2019
9b01caf
Remove hir::attributes module
eupn Aug 31, 2019
d2dea44
Extract and make text_to_tokentree function public
eupn Sep 4, 2019
2cf123e
Add attr macro expansion modules
eupn Sep 4, 2019
a75d6b4
Collect names of traits to derive, scaffold for trait implementations
eupn Sep 6, 2019
fe487f9
Use explicit module name for ast::MacroKind
eupn Sep 6, 2019
ca9f8d3
Invert the condition in expand_attr_macro
eupn Sep 7, 2019
a416972
rustfmt
eupn Sep 7, 2019
7c5ecfd
Add tests, simple empty implementation of derived traits
eupn Sep 7, 2019
8423efa
rustfmt
eupn Sep 7, 2019
04d5509
Rename collect_derive_attr to collect_attrs
eupn Sep 8, 2019
14b6a4e
Add test for #[derive(Trait)] type inference
eupn Sep 8, 2019
5bb0d47
Fix after rebase
eupn Sep 23, 2019
0d177ce
Fix parsing token trees produced by macro expansion
eupn Sep 23, 2019
f7eaa31
Remove dbg! from tests
eupn Sep 23, 2019
dbf2898
Implement collection of impl blocks from #[derive] attribute macro
eupn Sep 23, 2019
523774a
Add target item back to the scope since it was consumed by attr macro
eupn Sep 23, 2019
aa6d8b9
Fix post-rebase regression
eupn Sep 23, 2019
6b2474d
rustfmt
eupn Sep 23, 2019
8c813bc
TODO -> FIXME
eupn Sep 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/ra_hir/src/attr_macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use ra_syntax::ast::{self, AstNode};

#[cfg(test)]
mod tests;

mod derive;

pub fn expand_attr_macro(
attr_node: ast::Attr,
target_node: ast::ModuleItem,
) -> Option<tt::Subtree> {
if derive::is_derive_attr(&attr_node) {
return derive::expand_derive_attr(attr_node, target_node);
}

log::debug!("Unimplemented macro attr: {}", attr_node.syntax().to_string());
None
}
73 changes: 73 additions & 0 deletions crates/ra_hir/src/attr_macros/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use ra_syntax::{
ast::{self, NameOwner},
AstNode, SyntaxKind,
};

/// Checks whether `#[attr]` is in the `#[derive(<Traits>)]` form.
pub(crate) fn is_derive_attr(attr_node: &ast::Attr) -> bool {
if let Some((name, _args)) = attr_node.as_call() {
// FIXME: check for empty _args tree
name == "derive"
} else {
false
}
}

pub(crate) fn expand_derive_attr(
attr_node: ast::Attr,
target_node: ast::ModuleItem,
) -> Option<tt::Subtree> {
let traits_to_derive = collect_trait_names(&attr_node);

let tts = traits_to_derive
.into_iter()
.flatten()
.map(|tr| implement_trait_simple(&tr, &target_node))
.flatten()
.map(|subtree| tt::TokenTree::Subtree(subtree))
.collect::<Vec<_>>();

if !tts.is_empty() {
let tt = tt::Subtree { delimiter: tt::Delimiter::None, token_trees: tts };
Some(tt)
} else {
None
}
}

fn collect_trait_names(attr_node: &ast::Attr) -> Option<Vec<String>> {
if let Some((_, tt)) = attr_node.as_call() {
let items = tt
.syntax()
.children_with_tokens()
.into_iter()
.filter(|token| token.kind() == SyntaxKind::IDENT)
.map(|c| c.to_string())
.collect::<Vec<_>>();

Some(items)
} else {
None
}
}

fn item_name(item: &ast::ModuleItem) -> Option<String> {
if let Some(s) = ast::StructDef::cast(item.syntax().clone()) {
s.name().and_then(|n| Some(n.text().to_string()))
} else if let Some(e) = ast::EnumDef::cast(item.syntax().clone()) {
e.name().and_then(|n| Some(n.text().to_string()))
} else {
None
}
}

fn implement_trait_simple(trait_name: &str, target: &ast::ModuleItem) -> Option<tt::Subtree> {
if let Some(name) = item_name(target) {
let impl_code = format!("impl {} for {} {{}}", trait_name, name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't work for generic types, but we can fix that later

let tt = mbe::text_to_tokentree(&impl_code);

return Some(tt);
}

None
}
1 change: 1 addition & 0 deletions crates/ra_hir/src/attr_macros/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod derive;
52 changes: 52 additions & 0 deletions crates/ra_hir/src/attr_macros/tests/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::attr_macros::expand_attr_macro;
use crate::name::AsName;
use ra_syntax::{ast, AstNode, SyntaxNode};

fn expand_attr_macro_for(code: &str) -> Option<tt::Subtree> {
let ast = ast::SourceFile::parse(code).ok().unwrap().syntax().clone();

let attr_node = ast.descendants().find_map(|item| ast::Attr::cast(item)).unwrap();
let target_node = ast.children().find_map(|item| ast::ModuleItem::cast(item)).unwrap();

expand_attr_macro(attr_node, target_node)
}

fn has_trait_implemented(trait_name: &str, ast: &SyntaxNode) -> bool {
let impl_blocks = ast.descendants().map(|node| ast::ImplBlock::cast(node.clone())).flatten();

for impl_block in impl_blocks {
if let Some(ast::TypeRef::PathType(path_type)) = impl_block.target_trait() {
let name = path_type
.path()
.unwrap()
.segment()
.unwrap()
.name_ref()
.unwrap()
.as_name()
.to_string();

if name == trait_name {
return true;
}
}
}

false
}

#[test]
pub fn implement_trait_simple() {
let tt = expand_attr_macro_for("#[derive(Copy, Clone, Debug)] struct S {}").unwrap();
let code = tt.to_string();
let ast_struct = ast::SourceFile::parse(&code).ok().unwrap().syntax().clone();

let tt = expand_attr_macro_for("#[derive(Copy, Clone, Debug)] enum S {}").unwrap();
let code = tt.to_string();
let ast_enum = ast::SourceFile::parse(&code).ok().unwrap().syntax().clone();

for trait_name in ["Copy", "Clone", "Debug"].into_iter() {
assert!(has_trait_implemented(trait_name, &ast_struct));
assert!(has_trait_implemented(trait_name, &ast_enum));
}
}
2 changes: 1 addition & 1 deletion crates/ra_hir/src/expr/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ where

if let Some(path) = e.path().and_then(Path::from_ast) {
if let Some(def) = self.resolver.resolve_path_as_macro(self.db, &path) {
let call_id = MacroCallLoc { def: def.id, ast_id }.id(self.db);
let call_id = MacroCallLoc::Macro { def: def.id, ast_id }.id(self.db);
let file_id = call_id.as_file(MacroFileKind::Expr);
if let Some(node) = self.db.parse_or_expand(file_id) {
if let Some(expr) = ast::Expr::cast(node) {
Expand Down
130 changes: 91 additions & 39 deletions crates/ra_hir/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ra_prof::profile;
use ra_syntax::{ast, AstNode, Parse, SyntaxNode};

use crate::{
attr_macros,
db::{AstDatabase, DefDatabase, InternDatabase},
AstId, FileAstId, Module, Source,
};
Expand Down Expand Up @@ -43,7 +44,12 @@ impl HirFileId {
HirFileIdRepr::File(file_id) => file_id,
HirFileIdRepr::Macro(macro_file) => {
let loc = macro_file.macro_call_id.loc(db);
loc.ast_id.file_id().original_file(db)
match loc {
MacroCallLoc::Macro { ast_id, .. } => ast_id.file_id().original_file(db),
MacroCallLoc::Attribute { attr_ast_id, .. } => {
attr_ast_id.file_id().original_file(db)
}
}
}
}
}
Expand Down Expand Up @@ -139,27 +145,52 @@ pub(crate) fn macro_def_query(db: &impl AstDatabase, id: MacroDefId) -> Option<A

pub(crate) fn macro_arg_query(db: &impl AstDatabase, id: MacroCallId) -> Option<Arc<tt::Subtree>> {
let loc = id.loc(db);
let macro_call = loc.ast_id.to_node(db);
let arg = macro_call.token_tree()?;
let (tt, _) = mbe::ast_to_token_tree(&arg)?;
Some(Arc::new(tt))

match loc {
MacroCallLoc::Macro { ast_id, .. } => {
let macro_call = ast_id.to_node(db);
let arg = macro_call.token_tree()?;
let (tt, _) = mbe::ast_to_token_tree(&arg)?;
Some(Arc::new(tt))
}

MacroCallLoc::Attribute { .. } => None,
}
}

pub(crate) fn macro_expand_query(
db: &impl AstDatabase,
id: MacroCallId,
) -> Result<Arc<tt::Subtree>, String> {
let loc = id.loc(db);
let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;

let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?;
// Set a hard limit for the expanded tt
let count = tt.count();
if count > 65536 {
return Err(format!("Total tokens count exceed limit : count = {}", count));
match loc {
MacroCallLoc::Macro { ast_id: _, def } => {
let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;

let macro_rules = db.macro_def(def).ok_or("Fail to find macro definition")?;
let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?;
// Set a hard limit for the expanded tt
let count = tt.count();
if count > 65536 {
return Err(format!("Total tokens count exceed limit : count = {}", count));
}
Ok(Arc::new(tt))
}

MacroCallLoc::Attribute { attr_ast_id, target_ast_id } => {
let attr_node = attr_ast_id.to_node(db);
let target_node = target_ast_id.to_node(db);

let tt = attr_macros::expand_attr_macro(attr_node, target_node);
if let Some(tt) = tt {
Ok(Arc::new(tt))
} else {
// Return empty token tree
Ok(Arc::new(tt::Subtree { delimiter: tt::Delimiter::None, token_trees: vec![] }))
}
}
}
Ok(Arc::new(tt))
}

macro_rules! impl_intern_key {
Expand All @@ -182,9 +213,10 @@ pub struct MacroCallId(salsa::InternId);
impl_intern_key!(MacroCallId);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MacroCallLoc {
pub(crate) def: MacroDefId,
pub(crate) ast_id: AstId<ast::MacroCall>,
pub enum MacroCallLoc {
Macro { def: MacroDefId, ast_id: AstId<ast::MacroCall> },

Attribute { attr_ast_id: AstId<ast::Attr>, target_ast_id: AstId<ast::ModuleItem> },
}

impl MacroCallId {
Expand Down Expand Up @@ -364,29 +396,49 @@ impl AstItemDef<ast::TypeAliasDef> for TypeAliasId {
impl MacroCallId {
pub fn debug_dump(self, db: &impl AstDatabase) -> String {
let loc = self.loc(db);
let node = loc.ast_id.to_node(db);
let syntax_str = {
let mut res = String::new();
node.syntax().text().for_each_chunk(|chunk| {
if !res.is_empty() {
res.push(' ')
}
res.push_str(chunk)
});
res
};

// dump the file name
let file_id: HirFileId = self.loc(db).ast_id.file_id();
let original = file_id.original_file(db);
let macro_rules = db.macro_def(loc.def);

format!(
"macro call [file: {:?}] : {}\nhas rules: {}",
db.file_relative_path(original),
syntax_str,
macro_rules.is_some()
)
match loc {
MacroCallLoc::Macro { ast_id, def } => {
let node = ast_id.to_node(db);
let syntax_str = {
let mut res = String::new();
node.syntax().text().for_each_chunk(|chunk| {
if !res.is_empty() {
res.push(' ')
}
res.push_str(chunk)
});
res
};

// dump the file name
let file_id: HirFileId = ast_id.file_id();
let original = file_id.original_file(db);
let macro_rules = db.macro_def(def);

format!(
"macro call [file: {:?}] : {}\nhas rules: {}",
db.file_relative_path(original),
syntax_str,
macro_rules.is_some()
)
}

MacroCallLoc::Attribute { attr_ast_id, target_ast_id } => {
let attr_node = attr_ast_id.to_node(db);
let target_node = target_ast_id.to_node(db);

// dump the file name
let file_id: HirFileId = attr_ast_id.file_id();
let original = file_id.original_file(db);

format!(
"attribute macro [file: {:?}] : {} applied to {}",
db.file_relative_path(original),
attr_node.syntax().to_string(),
target_node.syntax().to_string(),
)
}
}
}
}

Expand Down
40 changes: 33 additions & 7 deletions crates/ra_hir/src/impl_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,42 @@ impl ModuleImplBlocks {
ast::ItemOrMacro::Item(_) => (),
ast::ItemOrMacro::Macro(macro_call) => {
//FIXME: we should really cut down on the boilerplate required to process a macro
let ast_id = db.ast_id_map(file_id).ast_id(&macro_call).with_file_id(file_id);
if let Some(path) = macro_call.path().and_then(Path::from_ast) {
if let Some(def) = self.module.resolver(db).resolve_path_as_macro(db, &path)
{
let call_id = MacroCallLoc { def: def.id, ast_id }.id(db);
let file_id = call_id.as_file(MacroFileKind::Items);
match macro_call {
ast::MacroKind::Call(macro_call) => {
let ast_id =
db.ast_id_map(file_id).ast_id(&macro_call).with_file_id(file_id);
if let Some(path) = macro_call.path().and_then(Path::from_ast) {
if let Some(def) =
self.module.resolver(db).resolve_path_as_macro(db, &path)
{
let call_id =
MacroCallLoc::Macro { def: def.id, ast_id }.id(db);
let file_id = call_id.as_file(MacroFileKind::Items);
if let Some(item_list) =
db.parse_or_expand(file_id).and_then(ast::MacroItems::cast)
{
self.collect_from_item_owner(
db, source_map, &item_list, file_id,
)
}
}
}
}

ast::MacroKind::Attr { attr, target } => {
let attr_ast_id =
db.ast_id_map(file_id).ast_id(&attr).with_file_id(file_id);
let target_ast_id =
db.ast_id_map(file_id).ast_id(&target).with_file_id(file_id);

let call_loc =
MacroCallLoc::Attribute { attr_ast_id, target_ast_id }.id(db);
let file_id = call_loc.as_file(MacroFileKind::Items);

if let Some(item_list) =
db.parse_or_expand(file_id).and_then(ast::MacroItems::cast)
{
self.collect_from_item_owner(db, source_map, &item_list, file_id)
self.collect_from_item_owner(db, source_map, &item_list, file_id);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/ra_hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ mod expr;
mod lang_item;
mod generics;
mod resolve;
mod attr_macros;
pub mod diagnostics;

mod code_model;
Expand Down
Loading