diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs index 0a70c63c00..fde50f7e6d 100644 --- a/crates/ra_hir_expand/src/builtin_derive.rs +++ b/crates/ra_hir_expand/src/builtin_derive.rs @@ -1,8 +1,15 @@ //! Builtin derives. -use crate::db::AstDatabase; -use crate::{name, MacroCallId, MacroDefId, MacroDefKind}; -use crate::quote; +use log::debug; + +use ra_parser::FragmentKind; +use ra_syntax::{ + ast::{self, AstNode, ModuleItemOwner, NameOwner, TypeParamsOwner}, + match_ast, +}; + +use crate::db::AstDatabase; +use crate::{name, quote, MacroCallId, MacroDefId, MacroDefKind}; macro_rules! register_builtin { ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { @@ -41,13 +48,79 @@ register_builtin! { (CLONE_TRAIT, Clone) => clone_expand } +struct BasicAdtInfo { + name: tt::Ident, + type_params: usize, +} + +fn parse_adt(tt: &tt::Subtree) -> Result { + let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, FragmentKind::Items)?; // FragmentKind::Items doesn't parse attrs? + let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| { + debug!("derive node didn't parse"); + mbe::ExpandError::UnexpectedToken + })?; + let item = macro_items.items().next().ok_or_else(|| { + debug!("no module item parsed"); + mbe::ExpandError::NoMatchingRule + })?; + let node = item.syntax(); + let (name, params) = match_ast! { + match node { + ast::StructDef(it) => { (it.name(), it.type_param_list()) }, + ast::EnumDef(it) => { (it.name(), it.type_param_list()) }, + ast::UnionDef(it) => { (it.name(), it.type_param_list()) }, + _ => { + debug!("unexpected node is {:?}", node); + return Err(mbe::ExpandError::ConversionError) + }, + } + }; + let name = name.ok_or_else(|| { + debug!("parsed item has no name"); + mbe::ExpandError::NoMatchingRule + })?; + let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| { + debug!("name token not found"); + mbe::ExpandError::ConversionError + })?; + let name_token = tt::Ident { id: name_token_id, text: name.text().clone() }; + let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count()); + Ok(BasicAdtInfo { name: name_token, type_params }) +} + +fn make_type_args(n: usize, bound: Vec) -> Vec { + let mut result = Vec::::new(); + result.push(tt::Leaf::Punct(tt::Punct { char: '<', spacing: tt::Spacing::Alone }).into()); + for i in 0..n { + if i > 0 { + result + .push(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone }).into()); + } + result.push( + tt::Leaf::Ident(tt::Ident { + id: tt::TokenId::unspecified(), + text: format!("T{}", i).into(), + }) + .into(), + ); + result.extend(bound.iter().cloned()); + } + result.push(tt::Leaf::Punct(tt::Punct { char: '>', spacing: tt::Spacing::Alone }).into()); + result +} + fn copy_expand( _db: &dyn AstDatabase, _id: MacroCallId, - _tt: &tt::Subtree, + tt: &tt::Subtree, ) -> Result { + let info = parse_adt(tt)?; + let name = info.name; + let bound = (quote! { : std::marker::Copy }).token_trees; + let type_params = make_type_args(info.type_params, bound); + let type_args = make_type_args(info.type_params, Vec::new()); let expanded = quote! { - impl Copy for Foo {} + impl ##type_params std::marker::Copy for #name ##type_args {} }; Ok(expanded) } @@ -55,10 +128,110 @@ fn copy_expand( fn clone_expand( _db: &dyn AstDatabase, _id: MacroCallId, - _tt: &tt::Subtree, + tt: &tt::Subtree, ) -> Result { + let info = parse_adt(tt)?; + let name = info.name; + let bound = (quote! { : std::clone::Clone }).token_trees; + let type_params = make_type_args(info.type_params, bound); + let type_args = make_type_args(info.type_params, Vec::new()); let expanded = quote! { - impl Clone for Foo {} + impl ##type_params std::clone::Clone for #name ##type_args {} }; Ok(expanded) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc, MacroFileKind}; + use ra_db::{fixture::WithFixture, SourceDatabase}; + + fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String { + let (db, file_id) = TestDB::with_single_file(&s); + let parsed = db.parse(file_id); + let items: Vec<_> = + parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect(); + + let ast_id_map = db.ast_id_map(file_id.into()); + + // the first one should be a macro_rules + let def = + MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) }; + + let loc = MacroCallLoc { + def, + kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))), + }; + + let id = db.intern_macro(loc); + let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Items)).unwrap(); + + // FIXME text() for syntax nodes parsed from token tree looks weird + // because there's no whitespace, see below + parsed.text().to_string() + } + + #[test] + fn test_copy_expand_simple() { + let expanded = expand_builtin_derive( + r#" + #[derive(Copy)] + struct Foo; +"#, + BuiltinDeriveExpander::Copy, + ); + + assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}"); + } + + #[test] + fn test_copy_expand_with_type_params() { + let expanded = expand_builtin_derive( + r#" + #[derive(Copy)] + struct Foo; +"#, + BuiltinDeriveExpander::Copy, + ); + + assert_eq!( + expanded, + "implstd::marker::CopyforFoo{}" + ); + } + + #[test] + fn test_copy_expand_with_lifetimes() { + let expanded = expand_builtin_derive( + r#" + #[derive(Copy)] + struct Foo; +"#, + BuiltinDeriveExpander::Copy, + ); + + // We currently just ignore lifetimes + + assert_eq!( + expanded, + "implstd::marker::CopyforFoo{}" + ); + } + + #[test] + fn test_clone_expand() { + let expanded = expand_builtin_derive( + r#" + #[derive(Clone)] + struct Foo; +"#, + BuiltinDeriveExpander::Clone, + ); + + assert_eq!( + expanded, + "implstd::clone::CloneforFoo{}" + ); + } +} diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs index 65a35e52fa..4f698ff134 100644 --- a/crates/ra_hir_expand/src/quote.rs +++ b/crates/ra_hir_expand/src/quote.rs @@ -60,6 +60,15 @@ macro_rules! __quote { } }; + ( ## $first:ident $($tail:tt)* ) => { + { + let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::>(); + let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + // Brace ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) }; // Bracket @@ -85,6 +94,7 @@ macro_rules! __quote { ( & ) => {$crate::__quote!(@PUNCT '&')}; ( , ) => {$crate::__quote!(@PUNCT ',')}; ( : ) => {$crate::__quote!(@PUNCT ':')}; + ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; ( . ) => {$crate::__quote!(@PUNCT '.')}; ( $first:tt $($tail:tt)+ ) => { diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index 0d9a35ce08..9c29a054e2 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs @@ -266,3 +266,54 @@ fn main() { "### ); } + +#[test] +fn infer_derive_clone_simple() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std +#[derive(Clone)] +struct S; +fn test() { + S.clone()<|>; +} + +//- /lib.rs crate:std +#[prelude_import] +use clone::*; +mod clone { + trait Clone { + fn clone(&self) -> Self; + } +} +"#, + ); + assert_eq!("S", type_at_pos(&db, pos)); +} + +#[test] +fn infer_derive_clone_with_params() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std +#[derive(Clone)] +struct S; +#[derive(Clone)] +struct Wrapper(T); +struct NonClone; +fn test() { + (Wrapper(S).clone(), Wrapper(NonClone).clone())<|>; +} + +//- /lib.rs crate:std +#[prelude_import] +use clone::*; +mod clone { + trait Clone { + fn clone(&self) -> Self; + } +} +"#, + ); + assert_eq!("(Wrapper, {unknown})", type_at_pos(&db, pos)); +}