use proc_macro2::{TokenStream, TokenTree};
use serde::{Deserialize, Serialize};
use syn::parse::{ParseStream, Parser};
use syn::{Attribute, Fields, ImplItem, Item, TraitItem, Type, UseTree, Visibility};
use crate::{HashSet, ItemKind, serde_names::Container};
pub(crate) const SERDE_MARKER: &str = "<serde>";
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub(crate) struct FileItems {
pub(crate) items: Vec<Decl>,
pub(crate) impls: Vec<Impl>,
pub(crate) uses: Vec<Use>,
pub(crate) imports: Vec<Use>,
pub(crate) mods: Vec<ModDecl>,
pub(crate) includes: Vec<Include>,
pub(crate) invocations: Vec<Invocation>,
pub(crate) macro_rules: Vec<MacroRules>,
pub(crate) lends: Vec<Lend>,
pub(crate) params: Vec<String>,
}
impl FileItems {
pub(crate) fn nest(&mut self, prefix: &[String]) {
let nest = |module: &mut Vec<String>| {
module.splice(0..0, prefix.iter().cloned());
};
self.items.iter_mut().for_each(|d| nest(&mut d.path));
self.impls.iter_mut().for_each(|i| nest(&mut i.module));
self.uses.iter_mut().for_each(|u| nest(&mut u.module));
self.imports.iter_mut().for_each(|u| nest(&mut u.module));
self.mods.iter_mut().for_each(|m| nest(&mut m.module));
self.includes.iter_mut().for_each(|i| nest(&mut i.module));
self.invocations
.iter_mut()
.for_each(|i| nest(&mut i.module));
self.macro_rules
.iter_mut()
.for_each(|r| nest(&mut r.module));
self.lends.iter_mut().for_each(|l| nest(&mut l.module));
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Decl {
pub(crate) path: Vec<String>,
pub(crate) kind: ItemKind,
pub(crate) public: bool,
pub(crate) signature: Option<String>,
pub(crate) line: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Impl {
pub(crate) module: Vec<String>,
pub(crate) self_ty: Vec<String>,
pub(crate) trait_path: Option<Vec<String>>,
pub(crate) items: Vec<(String, ItemKind, bool, Option<String>, u32)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Use {
pub(crate) module: Vec<String>,
pub(crate) source: Vec<String>,
pub(crate) name: Option<String>,
pub(crate) public: bool,
pub(crate) line: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ModDecl {
pub(crate) module: Vec<String>,
pub(crate) name: String,
pub(crate) paths: Vec<String>,
pub(crate) conditional_paths: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Include {
pub(crate) module: Vec<String>,
pub(crate) path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Invocation {
pub(crate) module: Vec<String>,
pub(crate) name: String,
pub(crate) path: Vec<String>,
pub(crate) idents: Vec<String>,
pub(crate) tokens: Option<String>,
pub(crate) impl_header: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct MacroRules {
pub(crate) module: Vec<String>,
pub(crate) name: String,
pub(crate) idents: Vec<String>,
pub(crate) body: String,
pub(crate) exported: bool,
pub(crate) line: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Lend {
pub(crate) module: Vec<String>,
pub(crate) owner: Vec<String>,
pub(crate) source: Vec<String>,
}
pub(crate) fn parse(source: &str) -> Result<FileItems, String> {
let parsed = syn::parse_file(source)
.map(|file| {
let bom = if source.starts_with('\u{feff}') { 3 } else { 0 };
let skipped = bom + file.shebang.as_ref().map_or(0, String::len);
let mut collector = Collector {
out: FileItems::default(),
module: Vec::new(),
source: source.get(skipped..).unwrap_or_default(),
};
collector.items(&file.items);
collector.out
})
.map_err(|error| error_message(&error));
proc_macro2::extra::invalidate_current_thread_spans();
parsed
}
pub(crate) fn parse_expansion(source: &str) -> Result<FileItems, String> {
let parsed = (|input: ParseStream<'_>| -> syn::Result<FileItems> {
let _ = Attribute::parse_inner(input)?;
let mut collector = Collector {
out: FileItems::default(),
module: Vec::new(),
source,
};
while !input.is_empty() {
let item: Item = input.parse()?;
collector.items(std::slice::from_ref(&item));
}
Ok(collector.out)
})
.parse_str(source)
.map_err(|error| error_message(&error));
proc_macro2::extra::invalidate_current_thread_spans();
parsed
}
fn error_message(error: &syn::Error) -> String {
let start = error.span().start();
format!("{}:{}: {error}", start.line, start.column + 1)
}
type Member = (String, ItemKind, bool, Vec<String>, u32);
struct Collector<'s> {
out: FileItems,
module: Vec<String>,
source: &'s str,
}
impl Collector<'_> {
fn items(&mut self, items: &[Item]) {
for item in items {
match item {
Item::Const(item) => self.decl(&item.ident, ItemKind::Const, &item.vis),
Item::Enum(item) => {
self.enumeration(item);
self.generics(&item.generics);
}
Item::Fn(item) => {
let name = item.sig.ident.to_string();
self.decl(&item.sig.ident, ItemKind::Function, &item.vis);
let end = item.block.brace_token.span.open().byte_range().start;
let signature = self.signature(&item.sig, end);
self.sign(&[&name], ItemKind::Function, signature);
self.params(&item.sig);
}
Item::ForeignMod(foreign) => self.foreign_items(&foreign.items),
Item::Impl(item) => {
self.impl_block(item);
self.generics(&item.generics);
}
Item::Macro(item) => self.macro_item(item),
Item::Mod(item) => self.mod_item(item),
Item::Static(item) => {
self.decl(&item.ident, ItemKind::Static, &item.vis);
}
Item::Struct(item) => {
let container = Container::new(&item.attrs);
let fields = match &item.fields {
Fields::Named(fields) => named_fields(&fields.named, &container),
Fields::Unnamed(_) | Fields::Unit => Vec::new(),
};
let name = item.ident.to_string();
self.owner(
&name,
ItemKind::Struct,
&item.vis,
fields,
line(&item.ident),
);
self.generics(&item.generics);
}
Item::Trait(item) => {
self.trait_item(item);
self.generics(&item.generics);
}
Item::TraitAlias(item) => {
self.decl(&item.ident, ItemKind::TraitAlias, &item.vis);
self.generics(&item.generics);
}
Item::Type(item) => {
self.type_alias(item);
self.generics(&item.generics);
}
Item::Union(item) => {
let container = Container::new(&item.attrs);
let fields = named_fields(&item.fields.named, &container);
let name = item.ident.to_string();
self.owner(&name, ItemKind::Union, &item.vis, fields, line(&item.ident));
self.generics(&item.generics);
}
Item::Use(item) => {
let public = is_pub(&item.vis);
let exported = !matches!(item.vis, Visibility::Inherited);
self.uses(&item.tree, &mut Vec::new(), exported, public);
}
_ => {}
}
}
}
fn decl(&mut self, ident: &syn::Ident, kind: ItemKind, vis: &Visibility) {
self.push(&[&ident.to_string()], kind, is_pub(vis), line(ident));
}
fn push(&mut self, names: &[&str], kind: ItemKind, public: bool, line: u32) {
if names.contains(&"_") {
return;
}
let path = self.path(names);
self.out.items.push(Decl {
path,
kind,
public,
signature: None,
line,
});
}
fn path(&self, names: &[&str]) -> Vec<String> {
let mut path = self.module.clone();
path.extend(names.iter().map(|name| strip_raw(name).to_owned()));
path
}
fn sign(&mut self, names: &[&str], kind: ItemKind, signature: String) {
let path = self.path(names);
if let Some(decl) = self
.out
.items
.iter_mut()
.rev()
.find(|decl| decl.kind == kind && decl.path == path)
{
decl.signature = Some(signature);
}
}
fn signature(&self, signature: &syn::Signature, end: usize) -> String {
let close = signature.paren_token.span.close().byte_range().start;
let mut params = Vec::new();
for pair in signature.inputs.pairs() {
let stop = pair
.punct()
.map_or(close, |comma| comma.span.byte_range().start);
params.push(match pair.value() {
syn::FnArg::Receiver(receiver) => match &receiver.kind {
syn::ReceiverKind::Typed(colon, _) => {
self.tokens(colon.span.byte_range().end, stop)
}
syn::ReceiverKind::Reference(_, _, Some(_)) => "& mut self".to_owned(),
syn::ReceiverKind::Reference(_, _, None) => "& self".to_owned(),
_ => "self".to_owned(),
},
syn::FnArg::Typed(typed) => {
self.tokens(typed.colon_token.span.byte_range().end, stop)
}
});
}
if signature.variadic.is_some() {
params.push("...".to_owned());
}
let output = match &signature.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(arrow, _) => {
let stop = signature
.generics
.where_clause
.as_ref()
.map_or(end, |clause| clause.where_token.span.byte_range().start);
format!(" -> {}", self.tokens(arrow.spans[1].byte_range().end, stop))
}
};
format!("({}){output}", params.join(", "))
}
fn tokens(&self, start: usize, stop: usize) -> String {
let Some(text) = self.source.get(start..stop) else {
return String::new();
};
let mut out = Vec::new();
let mut at = 0;
for token in ra_ap_rustc_lexer::tokenize(text, ra_ap_rustc_lexer::FrontmatterAllowed::No) {
let len = usize::try_from(token.len).expect("token lengths fit in usize");
if !matches!(
token.kind,
ra_ap_rustc_lexer::TokenKind::Whitespace
| ra_ap_rustc_lexer::TokenKind::LineComment { .. }
| ra_ap_rustc_lexer::TokenKind::BlockComment { .. }
) {
out.push(&text[at..at + len]);
}
at += len;
}
out.join(" ")
}
fn owner(
&mut self,
name: &str,
kind: ItemKind,
vis: &Visibility,
members: Vec<Member>,
line: u32,
) {
let public = is_pub(vis);
self.push(&[name], kind, public, line);
for (member, member_kind, member_public, spellings, line) in members {
let public = public && member_public;
self.push(&[name, &member], member_kind, public, line);
self.spellings(&[name, &member], &spellings, public, line);
}
}
fn spellings(&mut self, owner: &[&str], spellings: &[String], public: bool, line: u32) {
if spellings.is_empty() {
return;
}
let mut names = owner.to_vec();
names.push(SERDE_MARKER);
self.push(&names, ItemKind::Serialized, public, line);
for spelling in spellings {
names.push(spelling);
self.push(&names, ItemKind::Serialized, public, line);
names.pop();
}
}
fn enumeration(&mut self, item: &syn::ItemEnum) {
let container = Container::new(&item.attrs);
let name = item.ident.to_string();
let variants = item
.variants
.iter()
.map(|v| {
let variant = v.ident.to_string();
let spellings = container.variant(strip_raw(&variant), &v.attrs);
(variant, ItemKind::Variant, true, spellings, line(&v.ident))
})
.collect();
self.owner(
&name,
ItemKind::Enum,
&item.vis,
variants,
line(&item.ident),
);
let public = is_pub(&item.vis);
for variant in &item.variants {
let Fields::Named(fields) = &variant.fields else {
continue;
};
let fields_container = container.variant_fields(&variant.attrs);
let variant_name = variant.ident.to_string();
for field in &fields.named {
let Some(ident) = &field.ident else { continue };
let field_name = ident.to_string();
self.push(
&[&name, &variant_name, &field_name],
ItemKind::Field,
public,
line(ident),
);
let spellings = fields_container.field(strip_raw(&field_name), &field.attrs);
self.spellings(
&[&name, &variant_name, &field_name],
&spellings,
public,
line(ident),
);
}
}
}
fn foreign_items(&mut self, items: &[syn::ForeignItem]) {
for item in items {
match item {
syn::ForeignItem::Fn(item) => {
let name = item.sig.ident.to_string();
self.decl(&item.sig.ident, ItemKind::Function, &item.vis);
let end = item.semi_token.span.byte_range().start;
let signature = self.signature(&item.sig, end);
self.sign(&[&name], ItemKind::Function, signature);
self.params(&item.sig);
}
syn::ForeignItem::Static(item) => {
self.decl(&item.ident, ItemKind::Static, &item.vis);
}
syn::ForeignItem::Type(item) => {
self.decl(&item.ident, ItemKind::TypeAlias, &item.vis);
}
syn::ForeignItem::Macro(item) => self.invocation(&item.mac, false),
_ => {}
}
}
}
fn macro_item(&mut self, item: &syn::ItemMacro) {
if let Some(ident) = &item.ident {
let name = ident.to_string();
let exported = item.attrs.iter().any(|a| a.path().is_ident("macro_export"));
self.push(&[&name], ItemKind::Macro, exported, line(ident));
self.out.macro_rules.push(MacroRules {
module: self.module.clone(),
name,
idents: idents(&item.mac.tokens),
body: item.mac.tokens.to_string(),
exported,
line: line(ident),
});
} else if let Some(path) = include_path(&item.mac) {
self.out.includes.push(Include {
module: self.module.clone(),
path,
});
} else {
self.invocation(&item.mac, true);
}
}
fn mod_item(&mut self, item: &syn::ItemMod) {
let name = strip_raw(&item.ident.to_string()).to_owned();
self.push(
&[&name],
ItemKind::Module,
is_pub(&item.vis),
line(&item.ident),
);
if let Some((_, items)) = &item.content {
self.module.push(name);
self.items(items);
self.module.pop();
} else {
let (paths, conditional_paths) = path_attributes(&item.attrs);
self.out.mods.push(ModDecl {
module: self.module.clone(),
name,
paths,
conditional_paths,
});
}
}
fn trait_item(&mut self, item: &syn::ItemTrait) {
let mut members = Vec::new();
let mut signatures = Vec::new();
for trait_item in &item.items {
let (name, kind, at) = match trait_item {
TraitItem::Const(c) => (c.ident.to_string(), ItemKind::AssociatedConst, &c.ident),
TraitItem::Fn(f) => {
self.params(&f.sig);
let end = f.default.as_ref().map_or_else(
|| {
f.semi_token
.as_ref()
.map_or(self.source.len(), |semi| semi.span.byte_range().start)
},
|block| block.brace_token.span.open().byte_range().start,
);
let name = f.sig.ident.to_string();
signatures.push((name.clone(), self.signature(&f.sig, end)));
(name, ItemKind::Method, &f.sig.ident)
}
TraitItem::Type(t) => (t.ident.to_string(), ItemKind::AssociatedType, &t.ident),
TraitItem::Macro(m) => {
self.invocation(&m.mac, false);
continue;
}
_ => continue,
};
members.push((name, kind, true, Vec::new(), line(at)));
}
let trait_name = item.ident.to_string();
for bound in &item.supertraits {
if let syn::TypeParamBound::Trait(bound) = bound {
self.out.lends.push(Lend {
module: self.module.clone(),
owner: vec![strip_raw(&trait_name).to_owned()],
source: path_segments(&bound.path),
});
}
}
self.owner(
&trait_name,
ItemKind::Trait,
&item.vis,
members,
line(&item.ident),
);
for (name, signature) in signatures {
self.sign(&[&trait_name, &name], ItemKind::Method, signature);
}
}
fn impl_block(&mut self, item: &syn::ItemImpl) {
for impl_item in &item.items {
match impl_item {
ImplItem::Macro(m) => {
self.invocation(&m.mac, true);
let start = item.impl_token.span.byte_range().start;
let end = item.brace_token.span.open().byte_range().start;
if let Some(call) = self.out.invocations.last_mut() {
call.impl_header = self.source.get(start..end).map(str::to_owned);
}
}
ImplItem::Fn(f) => self.params(&f.sig),
_ => {}
}
}
let Some(segments) = type_path(&item.self_ty) else {
return;
};
let of_trait = item.trait_.is_some();
if let Some((trait_path, _)) = &item.trait_ {
let is_deref = trait_path
.segments
.last()
.is_some_and(|s| s.ident == "Deref");
let target = item.items.iter().find_map(|impl_item| match impl_item {
ImplItem::Type(t) if t.ident == "Target" => type_path(&t.ty),
_ => None,
});
if is_deref && let Some(target) = target {
self.out.lends.push(Lend {
module: self.module.clone(),
owner: segments.clone(),
source: target,
});
}
}
let items = item
.items
.iter()
.filter_map(|impl_item| match impl_item {
ImplItem::Const(c) => Some((&c.ident, ItemKind::AssociatedConst, &c.vis, None)),
ImplItem::Fn(f) => {
let end = f.block.brace_token.span.open().byte_range().start;
let signature = (!of_trait).then(|| self.signature(&f.sig, end));
Some((&f.sig.ident, ItemKind::Method, &f.vis, signature))
}
ImplItem::Type(t) => Some((&t.ident, ItemKind::AssociatedType, &t.vis, None)),
_ => None,
})
.map(|(ident, kind, vis, signature)| {
let name = strip_raw(&ident.to_string()).to_owned();
(name, kind, of_trait || is_pub(vis), signature, line(ident))
})
.collect();
self.out.impls.push(Impl {
module: self.module.clone(),
self_ty: segments,
trait_path: item.trait_.as_ref().map(|(path, _)| path_segments(path)),
items,
});
}
fn type_alias(&mut self, item: &syn::ItemType) {
let name = item.ident.to_string();
self.decl(&item.ident, ItemKind::TypeAlias, &item.vis);
if let Some(source) = type_path(&item.ty) {
self.out.uses.push(Use {
module: self.module.clone(),
source,
name: Some(strip_raw(&name).to_owned()),
public: is_pub(&item.vis),
line: line(&item.ident),
});
}
}
fn uses(&mut self, tree: &UseTree, prefix: &mut Vec<String>, exported: bool, public: bool) {
match tree {
UseTree::Path(path) => {
prefix.push(strip_raw(&path.ident.to_string()).to_owned());
self.uses(&path.tree, prefix, exported, public);
prefix.pop();
}
UseTree::Name(name) => {
let at = line(&name.ident);
let ident = strip_raw(&name.ident.to_string()).to_owned();
let (source, name) = if ident == "self" {
(prefix.clone(), prefix.last().cloned())
} else {
let mut source = prefix.clone();
source.push(ident.clone());
(source, Some(ident))
};
if name.is_some() {
let decl = Use {
module: self.module.clone(),
source,
name,
public,
line: at,
};
if exported {
self.out.uses.push(decl);
} else {
self.out.imports.push(decl);
}
}
}
UseTree::Rename(rename) => {
let ident = strip_raw(&rename.ident.to_string()).to_owned();
let alias = strip_raw(&rename.rename.to_string()).to_owned();
if alias == "_" {
return;
}
let mut source = prefix.clone();
if ident != "self" {
source.push(ident);
}
self.out.uses.push(Use {
module: self.module.clone(),
source,
name: Some(alias),
public,
line: line(&rename.rename),
});
}
UseTree::Glob(glob) if exported => self.out.uses.push(Use {
module: self.module.clone(),
source: prefix.clone(),
name: None,
public,
line: span_line(glob.star_token.spans[0]),
}),
UseTree::Group(group) => {
for tree in &group.items {
self.uses(tree, prefix, exported, public);
}
}
UseTree::Glob(_) => {}
}
}
fn params(&mut self, signature: &syn::Signature) {
let mut names = Vec::new();
for input in &signature.inputs {
if let syn::FnArg::Typed(typed) = input {
bound_names(&typed.pat, &mut names);
}
}
for name in names {
self.param(name);
}
self.generics(&signature.generics);
}
fn generics(&mut self, generics: &syn::Generics) {
for param in &generics.params {
let ident = match param {
syn::GenericParam::Type(param) => ¶m.ident,
syn::GenericParam::Const(param) => ¶m.ident,
syn::GenericParam::Lifetime(_) => continue,
};
self.param(strip_raw(&ident.to_string()).to_owned());
}
}
fn param(&mut self, name: String) {
if !self.out.params.contains(&name) {
self.out.params.push(name);
}
}
fn invocation(&mut self, mac: &syn::Macro, item_position: bool) {
let Some(last) = mac.path.segments.last() else {
return;
};
self.out.invocations.push(Invocation {
module: self.module.clone(),
name: strip_raw(&last.ident.to_string()).to_owned(),
path: mac
.path
.segments
.iter()
.map(|segment| strip_raw(&segment.ident.to_string()).to_owned())
.collect(),
idents: if item_position {
Vec::new()
} else {
idents(&mac.tokens)
},
tokens: item_position.then(|| mac.tokens.to_string()),
impl_header: None,
});
}
}
fn named_fields(
fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
container: &Container,
) -> Vec<Member> {
fields
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref()?;
let name = ident.to_string();
let spellings = container.field(strip_raw(&name), &field.attrs);
Some((
name,
ItemKind::Field,
is_pub(&field.vis),
spellings,
line(ident),
))
})
.collect()
}
fn line(ident: &syn::Ident) -> u32 {
span_line(ident.span())
}
fn span_line(span: proc_macro2::Span) -> u32 {
u32::try_from(span.start().line).expect("files have fewer than 2^32 lines")
}
fn bound_names(pattern: &syn::Pat, out: &mut Vec<String>) {
match pattern {
syn::Pat::Ident(ident) => {
out.push(strip_raw(&ident.ident.to_string()).to_owned());
if let Some((_, sub)) = &ident.subpat {
bound_names(sub, out);
}
}
syn::Pat::Tuple(tuple) => tuple.elems.iter().for_each(|p| bound_names(p, out)),
syn::Pat::TupleStruct(tuple) => tuple.elems.iter().for_each(|p| bound_names(p, out)),
syn::Pat::Slice(slice) => slice.elems.iter().for_each(|p| bound_names(p, out)),
syn::Pat::Or(or) => or.cases.iter().for_each(|p| bound_names(p, out)),
syn::Pat::Struct(structure) => structure
.fields
.iter()
.for_each(|field| bound_names(&field.pat, out)),
syn::Pat::Reference(reference) => bound_names(&reference.pat, out),
syn::Pat::Paren(paren) => bound_names(&paren.pat, out),
_ => {}
}
}
fn is_pub(vis: &Visibility) -> bool {
matches!(vis, Visibility::Public(_))
}
fn type_path(ty: &Type) -> Option<Vec<String>> {
let Type::Path(path) = ty else {
return None;
};
if path.qself.is_some() {
return None;
}
Some(path_segments(&path.path))
}
fn path_segments(path: &syn::Path) -> Vec<String> {
path.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()).to_owned())
.collect()
}
fn include_path(mac: &syn::Macro) -> Option<String> {
if !mac.path.is_ident("include") {
return None;
}
let literal: syn::LitStr = syn::parse2(mac.tokens.clone()).ok()?;
Some(literal.value())
}
pub(crate) fn text_idents(text: &str) -> Vec<String> {
let found = text
.parse::<TokenStream>()
.map(|tokens| idents(&tokens))
.unwrap_or_default();
proc_macro2::extra::invalidate_current_thread_spans();
found
}
fn idents(tokens: &TokenStream) -> Vec<String> {
let mut out = Vec::new();
let mut seen = HashSet::new();
let mut stack = vec![tokens.clone()];
while let Some(stream) = stack.pop() {
for tree in stream {
match tree {
TokenTree::Ident(ident) => {
let name = strip_raw(&ident.to_string()).to_owned();
if seen.insert(name.clone()) {
out.push(name);
}
}
TokenTree::Group(group) => stack.push(group.stream()),
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
}
}
}
out
}
fn path_attributes(attrs: &[Attribute]) -> (Vec<String>, Vec<String>) {
let mut direct = Vec::new();
let mut conditional = Vec::new();
for attr in attrs {
if attr.path().is_ident("path") {
if let Ok(value) = attr.meta.require_name_value() {
direct.extend(string_literal(&value.value));
}
} else if attr.path().is_ident("cfg_attr") {
let parsed = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
);
for meta in parsed.iter().flat_map(|metas| metas.iter().skip(1)) {
if let syn::Meta::NameValue(value) = meta
&& value.path.is_ident("path")
{
conditional.extend(string_literal(&value.value));
}
}
}
}
(direct, conditional)
}
fn string_literal(expr: &syn::Expr) -> Option<String> {
match expr {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) => Some(s.value()),
_ => None,
}
}
pub(crate) fn strip_raw(name: &str) -> &str {
name.strip_prefix("r#").unwrap_or(name)
}
#[cfg(test)]
mod tests {
use super::{FileItems, ItemKind, parse_expansion};
fn named(out: &FileItems) -> Vec<(Vec<String>, ItemKind, bool, Option<String>)> {
out.items
.iter()
.map(|d| (d.path.clone(), d.kind, d.public, d.signature.clone()))
.collect()
}
fn reexports(out: &FileItems) -> Vec<(Vec<String>, Option<String>, bool)> {
out.uses
.iter()
.map(|u| (u.source.clone(), u.name.clone(), u.public))
.collect()
}
#[test]
fn a_malformed_trailing_item_fails_the_whole_expansion() {
let source = "pub fn ok() -> u8 {\n 1\n}\nstruct;\n";
assert!(parse_expansion(source).is_err());
let unterminated = "pub fn ok() -> u8 {\n 1\n}\nfn broken(\n";
assert!(parse_expansion(unterminated).is_err());
}
#[test]
fn a_multi_item_expansion_keeps_its_declarations_and_signatures() {
let source = "\n#![doc = \"Geometry\"]\npub use crate::geo::{Circle, Circle as Ring};\npub trait Shape: Default {\n fn area(&self) -> f64;\n}\npub struct Point {\n pub x: u32,\n pub y: u32,\n}\npub fn distance(a: u32, b: &u32) -> f64 {\n 0.\n}\nimpl Point {\n pub fn norm(self, scale: u32) -> f64 {\n 0.\n }\n}\n";
let out = parse_expansion(source).expect("a valid expansion parses");
assert_eq!(
named(&out),
vec![
(vec!["Shape".into()], ItemKind::Trait, true, None),
(
vec!["Shape".into(), "area".into()],
ItemKind::Method,
true,
Some("(& self) -> f64".into())
),
(vec!["Point".into()], ItemKind::Struct, true, None),
(
vec!["Point".into(), "x".into()],
ItemKind::Field,
true,
None
),
(
vec!["Point".into(), "y".into()],
ItemKind::Field,
true,
None
),
(
vec!["distance".into()],
ItemKind::Function,
true,
Some("(u32, & u32) -> f64".into())
),
]
);
assert_eq!(
reexports(&out),
vec![
(
vec!["crate".into(), "geo".into(), "Circle".into()],
Some("Circle".into()),
true
),
(
vec!["crate".into(), "geo".into(), "Circle".into()],
Some("Ring".into()),
true
),
]
);
assert_eq!(out.impls[0].self_ty, ["Point"]);
let items: Vec<_> = out.impls[0]
.items
.iter()
.map(|(name, kind, public, signature, _)| {
(name.as_str(), *kind, *public, signature.as_deref())
})
.collect();
assert_eq!(
items,
[("norm", ItemKind::Method, true, Some("(self, u32) -> f64"))]
);
assert_eq!(out.lends[0].owner, ["Shape"]);
assert_eq!(out.lends[0].source, ["Default"]);
assert_eq!(out.params, ["a", "b", "scale"]);
}
}