-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
1,650 additions
and
582 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
//! This module provides parsing of `#[derive(...)]` attributes | ||
|
||
use darling::FromAttributes; | ||
use quote::ToTokens; | ||
use syn2::{punctuated::Punctuated, Attribute, Token}; | ||
|
||
use super::getset::GetSetDerive; | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
pub enum RustcDerive { | ||
Eq, | ||
PartialEq, | ||
Ord, | ||
PartialOrd, | ||
Clone, | ||
Copy, | ||
Hash, | ||
Default, | ||
Debug, | ||
} | ||
|
||
impl RustcDerive { | ||
fn try_from_path(path: &syn2::Path) -> Option<Self> { | ||
let Some(ident) = path.get_ident() else { | ||
return None; | ||
}; | ||
|
||
match ident.to_string().as_str() { | ||
"Eq" => Some(Self::Eq), | ||
"PartialEq" => Some(Self::PartialEq), | ||
"Ord" => Some(Self::Ord), | ||
"PartialOrd" => Some(Self::PartialOrd), | ||
"Clone" => Some(Self::Clone), | ||
"Copy" => Some(Self::Copy), | ||
"Hash" => Some(Self::Hash), | ||
"Default" => Some(Self::Default), | ||
"Debug" => Some(Self::Debug), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
pub enum Derive { | ||
Rustc(RustcDerive), | ||
GetSet(GetSetDerive), | ||
Other(String), | ||
} | ||
|
||
/// Represents a collection of all `#[derive(...)]` attributes placed on the item | ||
/// | ||
/// NOTE: strictly speaking, correctly parsing this is impossible, since it requires | ||
/// us to resolve the paths in the attributes, which is not possible in a proc-macro context. | ||
/// | ||
/// We just __hope__ that the user refers to the derives by their canonical names (no aliases). | ||
/// | ||
/// This, however, will mistakingly thing that `derive_more` derives are actually rustc's built-in ones. | ||
/// | ||
/// Care should be taken, and it should be documented in the macro APIs that use this. | ||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
pub struct DeriveAttr { | ||
pub derives: Vec<Derive>, | ||
} | ||
|
||
impl FromAttributes for DeriveAttr { | ||
fn from_attributes(attrs: &[Attribute]) -> darling::Result<Self> { | ||
let mut derives = Vec::new(); | ||
let mut accumulator = darling::error::Accumulator::default(); | ||
|
||
for attr in attrs { | ||
if attr.path().is_ident("derive") { | ||
let Some(list) = accumulator.handle(attr.meta.require_list().map_err(Into::into)) else { | ||
continue | ||
}; | ||
let Some(paths) = accumulator.handle( | ||
list.parse_args_with(Punctuated::<syn2::Path, Token![,]>::parse_terminated).map_err(Into::into) | ||
) else { | ||
continue | ||
}; | ||
|
||
for path in paths { | ||
let derive = if let Some(derive) = RustcDerive::try_from_path(&path) { | ||
Derive::Rustc(derive) | ||
} else if let Some(derive) = GetSetDerive::try_from_path(&path) { | ||
Derive::GetSet(derive) | ||
} else { | ||
Derive::Other(path.to_token_stream().to_string()) | ||
}; | ||
|
||
// I __think__ it's an error to use the same derive twice | ||
// I don't think we care in this case though | ||
derives.push(derive); | ||
} | ||
} | ||
} | ||
|
||
accumulator.finish_with(Self { derives }) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use darling::FromAttributes; | ||
use proc_macro2::TokenStream; | ||
use quote::quote; | ||
use syn2::parse::ParseStream; | ||
|
||
use super::{Derive, DeriveAttr, GetSetDerive, RustcDerive}; | ||
|
||
fn parse_derives(attrs: TokenStream) -> darling::Result<DeriveAttr> { | ||
struct Attributes(Vec<syn2::Attribute>); | ||
|
||
impl syn2::parse::Parse for Attributes { | ||
fn parse(input: ParseStream) -> syn2::Result<Self> { | ||
Ok(Self(input.call(syn2::Attribute::parse_outer)?)) | ||
} | ||
} | ||
|
||
let attrs = syn2::parse2::<Attributes>(attrs) | ||
.expect("Failed to parse tokens as outer attributes") | ||
.0; | ||
DeriveAttr::from_attributes(&attrs) | ||
} | ||
|
||
macro_rules! assert_derive_ok { | ||
($( #[$meta:meta] )*, | ||
$expected:expr | ||
) => { | ||
assert_eq!(parse_derives(quote!( | ||
$( #[$meta] )* | ||
)).unwrap(), | ||
$expected | ||
) | ||
}; | ||
} | ||
|
||
#[test] | ||
fn derive_rustc() { | ||
assert_derive_ok!( | ||
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Default, Debug)], | ||
DeriveAttr { | ||
derives: vec![ | ||
RustcDerive::Eq, | ||
RustcDerive::PartialEq, | ||
RustcDerive::Ord, | ||
RustcDerive::PartialOrd, | ||
RustcDerive::Clone, | ||
RustcDerive::Copy, | ||
RustcDerive::Hash, | ||
RustcDerive::Default, | ||
RustcDerive::Debug, | ||
].into_iter().map(Derive::Rustc).collect(), | ||
} | ||
) | ||
} | ||
|
||
#[test] | ||
fn derive_getset() { | ||
assert_derive_ok!( | ||
#[derive(Getters, Setters, MutGetters, CopyGetters)], | ||
DeriveAttr { | ||
derives: vec![ | ||
GetSetDerive::Getters, | ||
GetSetDerive::Setters, | ||
GetSetDerive::MutGetters, | ||
GetSetDerive::CopyGetters, | ||
].into_iter().map(Derive::GetSet).collect(), | ||
} | ||
) | ||
} | ||
|
||
#[test] | ||
fn derive_unknown() { | ||
assert_derive_ok!( | ||
#[derive(Aboba, Kek)], | ||
DeriveAttr { | ||
derives: vec![ | ||
"Aboba".to_string(), | ||
"Kek".to_string(), | ||
].into_iter().map(Derive::Other).collect(), | ||
} | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
use darling::FromAttributes; | ||
use syn2::Attribute; | ||
|
||
pub struct DocAttrs { | ||
pub attrs: Vec<Attribute>, | ||
} | ||
|
||
impl FromAttributes for DocAttrs { | ||
fn from_attributes(attrs: &[Attribute]) -> darling::Result<Self> { | ||
let mut docs = Vec::new(); | ||
for attr in attrs { | ||
if attr.path().is_ident("doc") { | ||
docs.push(attr.clone()); | ||
} | ||
} | ||
Ok(DocAttrs { attrs: docs }) | ||
} | ||
} |
Oops, something went wrong.