More accurate #[derive] parsing

This now allows full paths to the derive macro
This commit is contained in:
Jonas Schievink 2020-12-19 01:09:48 +01:00
parent c7b7c37ea5
commit ea5cc8d07a
4 changed files with 60 additions and 27 deletions

View File

@ -5,7 +5,7 @@
use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_expand::{hygiene::Hygiene, AstId, InFile};
use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
use itertools::Itertools;
use mbe::ast_to_token_tree;
use syntax::{
@ -19,7 +19,7 @@
db::DefDatabase,
item_tree::{ItemTreeId, ItemTreeNode},
nameres::ModuleSource,
path::ModPath,
path::{ModPath, PathKind},
src::HasChildSource,
AdtId, AttrDefId, Lookup,
};
@ -357,6 +357,46 @@ fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
};
Some(Attr { path, input })
}
/// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
/// to derive macros.
///
/// Returns `None` when the attribute is not a well-formed `#[derive]` attribute.
pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath>> {
if self.path.as_ident() != Some(&hir_expand::name![derive]) {
return None;
}
match &self.input {
Some(AttrInput::TokenTree(args)) => {
let mut counter = 0;
let paths = args
.token_trees
.iter()
.group_by(move |tt| {
match tt {
tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
counter += 1;
}
_ => {}
}
counter
})
.into_iter()
.map(|(_, tts)| {
let segments = tts.filter_map(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
_ => None,
});
ModPath::from_segments(PathKind::Plain, segments)
})
.collect::<Vec<_>>();
Some(paths.into_iter())
}
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
@ -384,7 +424,7 @@ pub fn exists(self) -> bool {
self.attrs().next().is_some()
}
fn attrs(self) -> impl Iterator<Item = &'a Attr> {
pub(crate) fn attrs(self) -> impl Iterator<Item = &'a Attr> {
let key = self.key;
self.attrs
.iter()

View File

@ -1289,20 +1289,20 @@ fn push_child_module(
}
fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) {
for derive_subtree in attrs.by_key("derive").tt_values() {
// for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree
for tt in &derive_subtree.token_trees {
let ident = match &tt {
tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => ident,
tt::TokenTree::Leaf(tt::Leaf::Punct(_)) => continue, // , is ok
_ => continue, // anything else would be an error (which we currently ignore)
};
let path = ModPath::from_tt_ident(ident);
let ast_id = AstIdWithPath::new(self.file_id, ast_id, path);
self.def_collector
.unexpanded_attribute_macros
.push(DeriveDirective { module_id: self.module_id, ast_id });
for derive in attrs.by_key("derive").attrs() {
match derive.parse_derive() {
Some(derive_macros) => {
for path in derive_macros {
let ast_id = AstIdWithPath::new(self.file_id, ast_id, path);
self.def_collector
.unexpanded_attribute_macros
.push(DeriveDirective { module_id: self.module_id, ast_id });
}
}
None => {
// FIXME: diagnose
log::debug!("malformed derive: {:?}", derive);
}
}
}
}

View File

@ -9,11 +9,8 @@
use crate::{body::LowerCtx, type_ref::LifetimeRef};
use base_db::CrateId;
use hir_expand::{
hygiene::Hygiene,
name::{AsName, Name},
};
use syntax::ast::{self};
use hir_expand::{hygiene::Hygiene, name::Name};
use syntax::ast;
use crate::{
type_ref::{TypeBound, TypeRef},
@ -56,11 +53,6 @@ pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -
ModPath { kind, segments }
}
/// Converts an `tt::Ident` into a single-identifier `Path`.
pub(crate) fn from_tt_ident(ident: &tt::Ident) -> ModPath {
ident.as_name().into()
}
/// Calls `cb` with all paths, represented by this use item.
pub(crate) fn expand_use_item(
item_src: InFile<ast::Use>,

View File

@ -152,6 +152,7 @@ macro_rules! known_names {
str,
// Special names
macro_rules,
derive,
doc,
cfg_attr,
// Components of known path (value or mod name)