|
1 | 1 | //! Builtin derives.
|
2 |
| -use crate::db::AstDatabase; |
3 |
| -use crate::{name, MacroCallId, MacroDefId, MacroDefKind}; |
4 | 2 |
|
5 |
| -use crate::quote; |
| 3 | +use log::debug; |
| 4 | + |
| 5 | +use ra_parser::FragmentKind; |
| 6 | +use ra_syntax::{ |
| 7 | + ast::{self, AstNode, ModuleItemOwner, NameOwner, TypeParamsOwner}, |
| 8 | + match_ast, |
| 9 | +}; |
| 10 | + |
| 11 | +use crate::db::AstDatabase; |
| 12 | +use crate::{name, quote, MacroCallId, MacroDefId, MacroDefKind}; |
6 | 13 |
|
7 | 14 | macro_rules! register_builtin {
|
8 | 15 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
|
@@ -41,24 +48,190 @@ register_builtin! {
|
41 | 48 | (CLONE_TRAIT, Clone) => clone_expand
|
42 | 49 | }
|
43 | 50 |
|
| 51 | +struct BasicAdtInfo { |
| 52 | + name: tt::Ident, |
| 53 | + type_params: usize, |
| 54 | +} |
| 55 | + |
| 56 | +fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, mbe::ExpandError> { |
| 57 | + let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, FragmentKind::Items)?; // FragmentKind::Items doesn't parse attrs? |
| 58 | + let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| { |
| 59 | + debug!("derive node didn't parse"); |
| 60 | + mbe::ExpandError::UnexpectedToken |
| 61 | + })?; |
| 62 | + let item = macro_items.items().next().ok_or_else(|| { |
| 63 | + debug!("no module item parsed"); |
| 64 | + mbe::ExpandError::NoMatchingRule |
| 65 | + })?; |
| 66 | + let node = item.syntax(); |
| 67 | + let (name, params) = match_ast! { |
| 68 | + match node { |
| 69 | + ast::StructDef(it) => { (it.name(), it.type_param_list()) }, |
| 70 | + ast::EnumDef(it) => { (it.name(), it.type_param_list()) }, |
| 71 | + ast::UnionDef(it) => { (it.name(), it.type_param_list()) }, |
| 72 | + _ => { |
| 73 | + debug!("unexpected node is {:?}", node); |
| 74 | + return Err(mbe::ExpandError::ConversionError) |
| 75 | + }, |
| 76 | + } |
| 77 | + }; |
| 78 | + let name = name.ok_or_else(|| { |
| 79 | + debug!("parsed item has no name"); |
| 80 | + mbe::ExpandError::NoMatchingRule |
| 81 | + })?; |
| 82 | + let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| { |
| 83 | + debug!("name token not found"); |
| 84 | + mbe::ExpandError::ConversionError |
| 85 | + })?; |
| 86 | + let name_token = tt::Ident { id: name_token_id, text: name.text().clone() }; |
| 87 | + let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count()); |
| 88 | + Ok(BasicAdtInfo { name: name_token, type_params }) |
| 89 | +} |
| 90 | + |
| 91 | +fn make_type_args(n: usize, bound: Vec<tt::TokenTree>) -> Vec<tt::TokenTree> { |
| 92 | + let mut result = Vec::<tt::TokenTree>::new(); |
| 93 | + result.push(tt::Leaf::Punct(tt::Punct { char: '<', spacing: tt::Spacing::Alone }).into()); |
| 94 | + for i in 0..n { |
| 95 | + if i > 0 { |
| 96 | + result |
| 97 | + .push(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone }).into()); |
| 98 | + } |
| 99 | + result.push( |
| 100 | + tt::Leaf::Ident(tt::Ident { |
| 101 | + id: tt::TokenId::unspecified(), |
| 102 | + text: format!("T{}", i).into(), |
| 103 | + }) |
| 104 | + .into(), |
| 105 | + ); |
| 106 | + result.extend(bound.iter().cloned()); |
| 107 | + } |
| 108 | + result.push(tt::Leaf::Punct(tt::Punct { char: '>', spacing: tt::Spacing::Alone }).into()); |
| 109 | + result |
| 110 | +} |
| 111 | + |
44 | 112 | fn copy_expand(
|
45 | 113 | _db: &dyn AstDatabase,
|
46 | 114 | _id: MacroCallId,
|
47 |
| - _tt: &tt::Subtree, |
| 115 | + tt: &tt::Subtree, |
48 | 116 | ) -> Result<tt::Subtree, mbe::ExpandError> {
|
| 117 | + let info = parse_adt(tt)?; |
| 118 | + let name = info.name; |
| 119 | + let bound = (quote! { : std::marker::Copy }).token_trees; |
| 120 | + let type_params = make_type_args(info.type_params, bound); |
| 121 | + let type_args = make_type_args(info.type_params, Vec::new()); |
49 | 122 | let expanded = quote! {
|
50 |
| - impl Copy for Foo {} |
| 123 | + impl ##type_params std::marker::Copy for #name ##type_args {} |
51 | 124 | };
|
52 | 125 | Ok(expanded)
|
53 | 126 | }
|
54 | 127 |
|
55 | 128 | fn clone_expand(
|
56 | 129 | _db: &dyn AstDatabase,
|
57 | 130 | _id: MacroCallId,
|
58 |
| - _tt: &tt::Subtree, |
| 131 | + tt: &tt::Subtree, |
59 | 132 | ) -> Result<tt::Subtree, mbe::ExpandError> {
|
| 133 | + let info = parse_adt(tt)?; |
| 134 | + let name = info.name; |
| 135 | + let bound = (quote! { : std::clone::Clone }).token_trees; |
| 136 | + let type_params = make_type_args(info.type_params, bound); |
| 137 | + let type_args = make_type_args(info.type_params, Vec::new()); |
60 | 138 | let expanded = quote! {
|
61 |
| - impl Clone for Foo {} |
| 139 | + impl ##type_params std::clone::Clone for #name ##type_args {} |
62 | 140 | };
|
63 | 141 | Ok(expanded)
|
64 | 142 | }
|
| 143 | + |
| 144 | +#[cfg(test)] |
| 145 | +mod tests { |
| 146 | + use super::*; |
| 147 | + use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc, MacroFileKind}; |
| 148 | + use ra_db::{fixture::WithFixture, SourceDatabase}; |
| 149 | + |
| 150 | + fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String { |
| 151 | + let (db, file_id) = TestDB::with_single_file(&s); |
| 152 | + let parsed = db.parse(file_id); |
| 153 | + let items: Vec<_> = |
| 154 | + parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect(); |
| 155 | + |
| 156 | + let ast_id_map = db.ast_id_map(file_id.into()); |
| 157 | + |
| 158 | + // the first one should be a macro_rules |
| 159 | + let def = |
| 160 | + MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) }; |
| 161 | + |
| 162 | + let loc = MacroCallLoc { |
| 163 | + def, |
| 164 | + kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))), |
| 165 | + }; |
| 166 | + |
| 167 | + let id = db.intern_macro(loc); |
| 168 | + let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Items)).unwrap(); |
| 169 | + |
| 170 | + // FIXME text() for syntax nodes parsed from token tree looks weird |
| 171 | + // because there's no whitespace, see below |
| 172 | + parsed.text().to_string() |
| 173 | + } |
| 174 | + |
| 175 | + #[test] |
| 176 | + fn test_copy_expand_simple() { |
| 177 | + let expanded = expand_builtin_derive( |
| 178 | + r#" |
| 179 | + #[derive(Copy)] |
| 180 | + struct Foo; |
| 181 | +"#, |
| 182 | + BuiltinDeriveExpander::Copy, |
| 183 | + ); |
| 184 | + |
| 185 | + assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}"); |
| 186 | + } |
| 187 | + |
| 188 | + #[test] |
| 189 | + fn test_copy_expand_with_type_params() { |
| 190 | + let expanded = expand_builtin_derive( |
| 191 | + r#" |
| 192 | + #[derive(Copy)] |
| 193 | + struct Foo<A, B>; |
| 194 | +"#, |
| 195 | + BuiltinDeriveExpander::Copy, |
| 196 | + ); |
| 197 | + |
| 198 | + assert_eq!( |
| 199 | + expanded, |
| 200 | + "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" |
| 201 | + ); |
| 202 | + } |
| 203 | + |
| 204 | + #[test] |
| 205 | + fn test_copy_expand_with_lifetimes() { |
| 206 | + let expanded = expand_builtin_derive( |
| 207 | + r#" |
| 208 | + #[derive(Copy)] |
| 209 | + struct Foo<A, B, 'a, 'b>; |
| 210 | +"#, |
| 211 | + BuiltinDeriveExpander::Copy, |
| 212 | + ); |
| 213 | + |
| 214 | + // We currently just ignore lifetimes |
| 215 | + |
| 216 | + assert_eq!( |
| 217 | + expanded, |
| 218 | + "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" |
| 219 | + ); |
| 220 | + } |
| 221 | + |
| 222 | + #[test] |
| 223 | + fn test_clone_expand() { |
| 224 | + let expanded = expand_builtin_derive( |
| 225 | + r#" |
| 226 | + #[derive(Clone)] |
| 227 | + struct Foo<A, B>; |
| 228 | +"#, |
| 229 | + BuiltinDeriveExpander::Clone, |
| 230 | + ); |
| 231 | + |
| 232 | + assert_eq!( |
| 233 | + expanded, |
| 234 | + "impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}" |
| 235 | + ); |
| 236 | + } |
| 237 | +} |
0 commit comments