Implement derive(Copy, Clone) properly (well, kind of)

This commit is contained in:
Florian Diebold 2019-12-05 19:29:57 +01:00
parent ab4ecca210
commit db8a00bd99
3 changed files with 241 additions and 7 deletions

View file

@ -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<BasicAdtInfo, mbe::ExpandError> {
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<tt::TokenTree>) -> Vec<tt::TokenTree> {
let mut result = Vec::<tt::TokenTree>::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<tt::Subtree, mbe::ExpandError> {
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<tt::Subtree, mbe::ExpandError> {
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<A, B>;
"#,
BuiltinDeriveExpander::Copy,
);
assert_eq!(
expanded,
"impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}"
);
}
#[test]
fn test_copy_expand_with_lifetimes() {
let expanded = expand_builtin_derive(
r#"
#[derive(Copy)]
struct Foo<A, B, 'a, 'b>;
"#,
BuiltinDeriveExpander::Copy,
);
// We currently just ignore lifetimes
assert_eq!(
expanded,
"impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}"
);
}
#[test]
fn test_clone_expand() {
let expanded = expand_builtin_derive(
r#"
#[derive(Clone)]
struct Foo<A, B>;
"#,
BuiltinDeriveExpander::Clone,
);
assert_eq!(
expanded,
"impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}"
);
}
}

View file

@ -60,6 +60,15 @@ macro_rules! __quote {
}
};
( ## $first:ident $($tail:tt)* ) => {
{
let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>();
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)+ ) => {

View file

@ -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>(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<S>, {unknown})", type_at_pos(&db, pos));
}