mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 14:13:58 +00:00
Implement derive(Copy, Clone) properly (well, kind of)
This commit is contained in:
parent
ab4ecca210
commit
db8a00bd99
3 changed files with 241 additions and 7 deletions
|
@ -1,8 +1,15 @@
|
||||||
//! Builtin derives.
|
//! 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 {
|
macro_rules! register_builtin {
|
||||||
( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
|
( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
|
||||||
|
@ -41,13 +48,79 @@ register_builtin! {
|
||||||
(CLONE_TRAIT, Clone) => clone_expand
|
(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(
|
fn copy_expand(
|
||||||
_db: &dyn AstDatabase,
|
_db: &dyn AstDatabase,
|
||||||
_id: MacroCallId,
|
_id: MacroCallId,
|
||||||
_tt: &tt::Subtree,
|
tt: &tt::Subtree,
|
||||||
) -> Result<tt::Subtree, mbe::ExpandError> {
|
) -> 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! {
|
let expanded = quote! {
|
||||||
impl Copy for Foo {}
|
impl ##type_params std::marker::Copy for #name ##type_args {}
|
||||||
};
|
};
|
||||||
Ok(expanded)
|
Ok(expanded)
|
||||||
}
|
}
|
||||||
|
@ -55,10 +128,110 @@ fn copy_expand(
|
||||||
fn clone_expand(
|
fn clone_expand(
|
||||||
_db: &dyn AstDatabase,
|
_db: &dyn AstDatabase,
|
||||||
_id: MacroCallId,
|
_id: MacroCallId,
|
||||||
_tt: &tt::Subtree,
|
tt: &tt::Subtree,
|
||||||
) -> Result<tt::Subtree, mbe::ExpandError> {
|
) -> 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! {
|
let expanded = quote! {
|
||||||
impl Clone for Foo {}
|
impl ##type_params std::clone::Clone for #name ##type_args {}
|
||||||
};
|
};
|
||||||
Ok(expanded)
|
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>{}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
// Brace
|
||||||
( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
|
( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
|
||||||
// Bracket
|
// Bracket
|
||||||
|
@ -85,6 +94,7 @@ macro_rules! __quote {
|
||||||
( & ) => {$crate::__quote!(@PUNCT '&')};
|
( & ) => {$crate::__quote!(@PUNCT '&')};
|
||||||
( , ) => {$crate::__quote!(@PUNCT ',')};
|
( , ) => {$crate::__quote!(@PUNCT ',')};
|
||||||
( : ) => {$crate::__quote!(@PUNCT ':')};
|
( : ) => {$crate::__quote!(@PUNCT ':')};
|
||||||
|
( :: ) => {$crate::__quote!(@PUNCT ':', ':')};
|
||||||
( . ) => {$crate::__quote!(@PUNCT '.')};
|
( . ) => {$crate::__quote!(@PUNCT '.')};
|
||||||
|
|
||||||
( $first:tt $($tail:tt)+ ) => {
|
( $first:tt $($tail:tt)+ ) => {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue