From db6299a54a8c22f83106f8ca56bd35f4fbb1f252 Mon Sep 17 00:00:00 2001 From: Viktor Lott Date: Sat, 14 Jan 2023 00:08:14 +0100 Subject: [PATCH] Rebuilding the library --- Cargo.toml | 2 +- src/builder.rs | 251 +++++++++++++++++++++++++++++++++++++++----- src/docs/struct.md | 2 +- src/docs/type.md | 2 +- src/tools.rs | 72 +++---------- tests/typed-test.rs | 3 + 6 files changed, 243 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5dba53..85797d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ description = "Get the types of a struct" proc-macro2 = "1.0.49" quote = "1.0.23" rustfmt = "0.10.0" -syn = "1.0.107" +syn = { version = "1.0.107", features = [ "visit", "full" ]} [lib] proc-macro = true diff --git a/src/builder.rs b/src/builder.rs index 976a7f3..66971ab 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,48 +1,243 @@ use proc_macro::TokenStream; -use quote::{quote}; -use syn::{parse, Data, DeriveInput}; -use tools::{modify, format_code, doc_struct, TypeDecl, publicify}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; +use syn::{ + self, + parse::Parse, + parse_quote, token, + visit::{visit_type, visit_type_path, Visit}, + Attribute, Fields, Generics, Ident, Token, Type, Visibility, +}; +use std::{collections::HashSet}; -#[path = "tools.rs"] +use tools::{doc_struct, doc_type, format_code, modify}; + +#[path ="tools.rs"] mod tools; +struct TypeModule { + attrs: Vec, + vis: Visibility, + ident: Ident, + inner: TypeModuleInner, +} -pub fn codegen(_attr: TokenStream, item: TokenStream) -> TokenStream { - let ast: DeriveInput = parse(item.clone()).unwrap(); +struct TypeModuleInner { + type_decls: Vec, + struct_decl: TypeStructure, +} - let mut struct_entry = ast.clone(); +struct TypeStructure { + attrs: Vec, + vis: Visibility, + struct_token: Token![struct], + ident: Ident, + generics: Generics, + fields: Fields, + semi_colon: Option, +} - publicify(&mut struct_entry); +struct TypeDecl { + docs: Attribute, + ident: Ident, + ty: Type, +} - let Data::Struct(ref mut data_struct) = struct_entry.data else { - panic!("Cannot destruct Struct"); - }; +struct Source { + name: String, + code: String, +} - let parent = ast.ident.to_string(); - let original = format_code(item.to_string()); - let mut ty_decls: Vec = Vec::new(); +struct FieldTypeGenerics(HashSet); - for (index, field) in data_struct.fields.iter_mut().enumerate() { - let ident = modify(field, parent.as_str(), index); - ty_decls.push(TypeDecl::new(original.as_str(), ident, &field.ty)); +impl FieldTypeGenerics { + fn get_idents(ty: &Type) -> Self { + let mut field_type_generics = FieldTypeGenerics(<_>::default()); + visit_type(&mut field_type_generics, ty); + field_type_generics + } +} +impl<'ast> Visit<'ast> for FieldTypeGenerics { + fn visit_type_path(&mut self, i: &'ast syn::TypePath) { + if let Some(p) = i.path.segments.first() { + self.0.insert(p.ident.clone()); + } + visit_type_path(self, i); } +} + - let name = ast.ident; - let docs = doc_struct(parent.as_str(), original.as_str()); +impl Parse for TypeModule { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let code = format_code(input.to_string()); + println!("{}", code); - let output = quote!( - #[allow(non_snake_case)] - #[doc = #docs] - pub mod #name { - #![allow(non_camel_case_types)] + let mut attrs: Vec = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let struct_token: Token![struct] = input.parse()?; + let ident: Ident = input.parse()?; + let generics: Generics = input.parse()?; + let mut semi_colon: Option = None; - #(#ty_decls)* + let source = Source { + name: ident.to_string(), + code, + }; - #[doc = #docs] - #struct_entry + let mut type_decls: Vec = Vec::new(); + let mut fields: Fields; + + if input.peek(token::Brace) { + fields = Fields::Named(input.parse()?); + type_decls = parse_type_decls(&mut fields, &generics, &source); + } else if input.peek(token::Paren) { + fields = Fields::Unnamed(input.parse()?); + } else { + fields = Fields::Unit; } - ); + let docs = doc_struct( + source.name.as_str(), + source.code.as_str() + ); + + attrs.push(parse_quote!(#[doc = #docs])); + + + if input.peek(Token![;]) { + semi_colon = input.parse().ok(); + } + + let struct_decl = TypeStructure { + attrs: attrs.clone(), + vis: parse_quote!(pub), + struct_token, + ident: format_ident!("ty", span = proc_macro2::Span::call_site()), + generics, + fields: fields.clone(), + semi_colon, + }; + + + let inner = TypeModuleInner { + type_decls, + struct_decl, + }; + + Ok(Self { + attrs, + vis, + ident, + inner, + }) + } +} + + +impl ToTokens for TypeModule { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let attrs = &self.attrs; + let vis = &self.vis; + let ident = &self.ident; + let module_inner = &self.inner; + + + let type_module = quote!( + #[allow(non_snake_case)] + #(#attrs)* + #vis mod #ident { + #![allow(non_camel_case_types)] + + #module_inner + + } + ); + + tokens.append_all(type_module); + } +} + +impl ToTokens for TypeModuleInner { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let type_delcs = &self.type_decls; + let struct_decl = &self.struct_decl; + + let inner_decls = quote!( + #(#type_delcs)* + + #struct_decl + ); + + tokens.append_all(inner_decls) + } +} + +impl ToTokens for TypeStructure { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let attrs = &self.attrs; + let visibility = &self.vis; + let struct_token = &self.struct_token; + let ident = &self.ident; + let generics = &self.generics; + let fields = &self.fields; + let semi_colon = &self.semi_colon; + + let struct_decl = quote!( + #(#attrs)* + #visibility #struct_token #ident #generics #fields #semi_colon + ); + + tokens.append_all(struct_decl); + } +} + +impl ToTokens for TypeDecl { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let docs = &self.docs; + let ident = &self.ident; + let ty = &self.ty; + + tokens.append_all(quote!(#docs pub type #ident = #ty;)); + } +} + + + +impl TypeDecl { + pub fn new<'a>(source_code: &'a str, ident: &'a Ident, ty: &'a Type) -> Self { + let docs = doc_type(ident, ty, source_code); + let docs: Attribute = parse_quote!(#[doc = #docs]); + + Self { + docs, + ident: ident.clone(), + ty: ty.clone(), + } + } +} + +fn parse_type_decls(fields: &mut Fields, generics: &Generics, source: &Source) -> Vec { + let mut type_decls: Vec = Vec::new(); + + let param_generics = generics + .type_params() + .map(|tp| tp.ident.clone()) + .collect::>(); + + for (index, field) in fields.iter_mut().enumerate() { + let field_type_generics = FieldTypeGenerics::get_idents(&field.ty); + if param_generics.intersection(&field_type_generics.0).count() == 0 { + let field_ident = modify(field, source.name.as_str(), index); + type_decls.push(TypeDecl::new(source.code.as_str(), &field_ident, &field.ty)); + } + } + + type_decls +} + +pub fn codegen(_attr: TokenStream, item: TokenStream) -> TokenStream { + let type_module = syn::parse::(item).unwrap(); + let output = quote!(#type_module); output.into() } diff --git a/src/docs/struct.md b/src/docs/struct.md index e045846..bedce67 100644 --- a/src/docs/struct.md +++ b/src/docs/struct.md @@ -1,6 +1,6 @@ Use `{name}::ty` as a type. ``` -{original} +{source_code} ``` # Example ``` diff --git a/src/docs/type.md b/src/docs/type.md index 4f91da8..27fd956 100644 --- a/src/docs/type.md +++ b/src/docs/type.md @@ -2,6 +2,6 @@ Use `{name}` as a type. ``` type {name} = {ty}; -{original} +{source_code} ``` [Read more about `{ty}`][{ty}] \ No newline at end of file diff --git a/src/tools.rs b/src/tools.rs index f9629a5..2977d26 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -1,53 +1,23 @@ use ::rustfmt::{format_input, Input}; -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{Ident, Type, Field, parse_quote, DeriveInput}; -use std::{io::Sink, marker::PhantomData}; +use quote::{format_ident, ToTokens}; +use std::{io::Sink}; +use syn::{self, parse_quote, Field, Ident, Type}; -pub struct TypeDecl<'a> { - original: &'a str, - ident: Ident, - ty: Type, - _marker: PhantomData &'a ()> -} - -impl<'a> TypeDecl<'a> { - pub fn new(original: &'a str, ident: Ident, ty: &'a Type) -> Self { - Self { - original, - ident, - ty: ty.clone(), - _marker: PhantomData - } - } -} - -impl<'a> ToTokens for TypeDecl<'a> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let original = self.original; - let ident = &self.ident; - let ty = &self.ty; - let docu = doc_type(ident, ty, original); - let ty_decl = quote!(#[doc = #docu] pub type #ident = #ty;); - tokens.append_all(ty_decl); - } -} - -pub fn doc_type(name: &Ident, ty: &Type, original: &str) -> String { +pub fn doc_type(name: &Ident, ty: &Type, source_code: &str) -> String { let name = name.to_string(); - let ty = ty.to_token_stream().to_string().replace(" ", ""); + let ty = ty.to_token_stream().to_string().replace(' ', ""); format!( include_str!("docs/type.md"), - original = original, + source_code = source_code, name = name, ty = ty ) } -pub fn doc_field(name: &Ident, ty: &Type, parent: &str) -> String { +fn doc_field(name: &Ident, ty: &Type, parent: &str) -> String { let name = name.to_string(); - let ty = ty.to_token_stream().to_string().replace(" ", ""); + let ty = ty.to_token_stream().to_string().replace(' ', ""); format!( include_str!("docs/field.md"), @@ -57,47 +27,33 @@ pub fn doc_field(name: &Ident, ty: &Type, parent: &str) -> String { ) } -pub fn doc_struct(name: &str, original: &str) -> String { +pub fn doc_struct(name: &str, source_code: &str) -> String { format!( include_str!("docs/struct.md"), name = name, - original = original + source_code = source_code ) } - - pub fn format_code(orig: String) -> String { format_input(Input::Text(orig), &<_>::default(), None::<&mut Sink>) .map(|res| res.1.into_iter().next()) .ok() .flatten() .map(|m| m.1.to_string()) - .expect("Original input should be formatted") + .expect("source_code input should be formatted") } -pub fn create_ident(i: usize) -> impl Fn() -> Ident { - move || Ident::new(format!("ty_{i}").as_str(), proc_macro2::Span::call_site()) +pub fn create_ident(i: usize) -> impl Fn() -> Ident { + move || format_ident!("ty_{i}", span = proc_macro2::Span::call_site()) } pub fn modify(field: &mut Field, parent: &str, i: usize) -> Ident { let ident = field.ident.clone().unwrap_or_else(create_ident(i)); - let docu = doc_field( - &ident, - &field.ty, - parent, - ); + let docu = doc_field(&ident, &field.ty, parent); field.vis = parse_quote!(pub); field.attrs.push(parse_quote!(#[doc = #docu])); ident } - -pub fn publicify(ast: &mut DeriveInput) { - ast.ident = parse_quote!(ty); - ast.vis = parse_quote!(pub); -} - - - diff --git a/tests/typed-test.rs b/tests/typed-test.rs index 42bdaf6..7385d3b 100644 --- a/tests/typed-test.rs +++ b/tests/typed-test.rs @@ -12,6 +12,9 @@ struct Container { #[type_it] struct Tuple(i32, i32); +#[type_it] +struct Tuple2(i32, T); + fn main() { let current: Container::current = 10; let buffer: Container::buffer = vec![current];