From 07b5f3b0cab0501cc98014bf820a0e9c9503e57c Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Tue, 4 Jul 2023 18:28:15 +0800 Subject: [PATCH 1/5] imported bae2 into sea-orm local system --- Cargo.toml | 2 +- sea-orm-macros/Cargo.toml | 2 +- sea-orm-macros/sea-bae/Cargo.toml | 60 ++++ sea-orm-macros/sea-bae/Cargo.toml.orig | 30 ++ sea-orm-macros/sea-bae/src/lib.rs | 367 +++++++++++++++++++++++++ 5 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 sea-orm-macros/sea-bae/Cargo.toml create mode 100644 sea-orm-macros/sea-bae/Cargo.toml.orig create mode 100644 sea-orm-macros/sea-bae/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index fc5dffa02..b3a2e4af4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "sea-orm-macros", "sea-orm-codegen"] +members = [".", "sea-orm-macros", "sea-orm-codegen" , "sea-orm-macros/sea-bae"] [package] name = "sea-orm" diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 18de55760..22a512430 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" proc-macro = true [dependencies] -bae = { version = "1", package = "bae2", default-features = false, optional = true } +bae = { version = "1", path = "sea-bae", package = "sea-bae", default-features = false, optional = true } syn = { version = "2", default-features = false, features = ["parsing", "proc-macro", "derive", "printing"] } quote = { version = "1", default-features = false } heck = { version = "0.4", default-features = false } diff --git a/sea-orm-macros/sea-bae/Cargo.toml b/sea-orm-macros/sea-bae/Cargo.toml new file mode 100644 index 000000000..25a486733 --- /dev/null +++ b/sea-orm-macros/sea-bae/Cargo.toml @@ -0,0 +1,60 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "sea-bae" +version = "1.0.0" +authors = [ + "David Pedersen ", + "Naoki Ikeguchi ", +] +description = "A Rust proc-macro attribute parser" +homepage = "https://github.com/siketyan/bae2" +documentation = "https://docs.rs/bae2" +readme = "README.md" +keywords = [ + "macro", + "derive", + "attributes", + "proc", +] +categories = ["development-tools::procedural-macro-helpers"] +license = "MIT" +repository = "https://github.com/siketyan/bae2.git" + +[lib] +name = "sea_bae" +path = "src/lib.rs" +proc-macro = true + +[dependencies.heck] +version = "0.4.1" + +[dependencies.proc-macro-error] +version = "1" + +[dependencies.proc-macro2] +version = "1" + +[dependencies.quote] +version = "1" + +[dependencies.syn] +version = "2" +features = [ + "full", + "extra-traits", +] + +[dev-dependencies.trybuild] +version = "1" + diff --git a/sea-orm-macros/sea-bae/Cargo.toml.orig b/sea-orm-macros/sea-bae/Cargo.toml.orig new file mode 100644 index 000000000..9af5a23c8 --- /dev/null +++ b/sea-orm-macros/sea-bae/Cargo.toml.orig @@ -0,0 +1,30 @@ +[package] +name = "bae2" +description = "A Rust proc-macro attribute parser" +version = "1.0.0" +edition = "2021" +readme = "README.md" +license = "MIT" +homepage = "https://github.com/siketyan/bae2" +documentation = "https://docs.rs/bae2" +repository = "https://github.com/siketyan/bae2.git" +authors = [ + "David Pedersen ", + "Naoki Ikeguchi ", +] +keywords = ["macro", "derive", "attributes", "proc"] +categories = ["development-tools::procedural-macro-helpers"] + +[dependencies] +syn = { version = "2", features = ["full", "extra-traits"] } +quote = "1" +proc-macro2 = "1" +proc-macro-error = "1" +heck = "0.4.1" + +[dev_dependencies] +trybuild = "1" + +[lib] +proc-macro = true +path = "src/lib.rs" diff --git a/sea-orm-macros/sea-bae/src/lib.rs b/sea-orm-macros/sea-bae/src/lib.rs new file mode 100644 index 000000000..5da953d6b --- /dev/null +++ b/sea-orm-macros/sea-bae/src/lib.rs @@ -0,0 +1,367 @@ +//! `bae` is a crate for proc macro authors, which simplifies parsing of attributes. It is +//! heavily inspired by [`darling`](https://crates.io/crates/darling) but has a significantly +//! simpler API. +//! +//! ```rust +//! use bae2::FromAttributes; +//! +//! #[derive( +//! Debug, +//! Eq, +//! PartialEq, +//! +//! // This will add two functions: +//! // ``` +//! // fn from_attributes(attrs: &[syn::Attribute]) -> Result +//! // fn try_from_attributes(attrs: &[syn::Attribute]) -> Result, syn::Error> +//! // ``` +//! // +//! // `try_from_attributes` returns `Ok(None)` if the attribute is missing, `Ok(Some(_))` if +//! // its there and is valid, `Err(_)` otherwise. +//! FromAttributes, +//! )] +//! pub struct MyAttr { +//! // Anything that implements `syn::parse::Parse` is supported. +//! mandatory_type: syn::Type, +//! mandatory_ident: syn::Ident, +//! +//! // Fields wrapped in `Option` are optional and default to `None` if +//! // not specified in the attribute. +//! optional_missing: Option, +//! optional_given: Option, +//! +//! // A "switch" is something that doesn't take arguments. +//! // All fields with type `Option<()>` are considered swiches. +//! // They default to `None`. +//! switch: Option<()>, +//! } +//! +//! // `MyAttr` is now equipped to parse attributes named `my_attr`. For example: +//! // +//! // #[my_attr( +//! // switch, +//! // mandatory_ident = foo, +//! // mandatory_type = SomeType, +//! // optional_given = OtherType, +//! // )] +//! // struct Foo { +//! // ... +//! // } +//! +//! // the input and output type would normally be `proc_macro::TokenStream` but those +//! // types cannot be used outside the compiler itself. +//! fn my_proc_macro(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream { +//! let item_struct = syn::parse2::(input).unwrap(); +//! +//! let my_attr = MyAttr::from_attributes(&item_struct.attrs).unwrap(); +//! +//! assert_eq!( +//! my_attr.mandatory_type, +//! syn::parse_str::("SomeType").unwrap() +//! ); +//! +//! assert_eq!(my_attr.optional_missing, None); +//! +//! assert_eq!( +//! my_attr.optional_given, +//! Some(syn::parse_str::("OtherType").unwrap()) +//! ); +//! +//! assert_eq!(my_attr.mandatory_ident, syn::parse_str::("foo").unwrap()); +//! +//! assert_eq!(my_attr.switch.is_some(), true); +//! +//! // ... +//! # +//! # quote::quote! {} +//! } +//! # +//! # fn main() { +//! # let code = quote::quote! { +//! # #[other_random_attr] +//! # #[my_attr( +//! # switch, +//! # mandatory_ident = foo, +//! # mandatory_type = SomeType, +//! # optional_given = OtherType, +//! # )] +//! # struct Foo; +//! # }; +//! # my_proc_macro(code); +//! # } +//! ``` + +#![doc(html_root_url = "https://docs.rs/bae/0.1.7")] +#![allow(clippy::let_and_return)] +#![deny( + unused_variables, + dead_code, + unused_must_use, + unused_imports, + missing_docs +)] + +extern crate proc_macro; + +use heck::ToSnakeCase; +use proc_macro2::TokenStream; +use proc_macro_error::*; +use quote::*; +use syn::{spanned::Spanned, *}; + +/// See root module docs for more info. +#[proc_macro_derive(FromAttributes, attributes())] +#[proc_macro_error] +pub fn from_attributes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = parse_macro_input!(input as ItemStruct); + FromAttributes::new(item).expand().into() +} + +#[derive(Debug)] +struct FromAttributes { + item: ItemStruct, + tokens: TokenStream, +} + +impl FromAttributes { + fn new(item: ItemStruct) -> Self { + Self { + item, + tokens: TokenStream::new(), + } + } + + fn expand(mut self) -> TokenStream { + self.expand_from_attributes_method(); + self.expand_parse_impl(); + + if std::env::var("BAE_DEBUG").is_ok() { + eprintln!("{}", self.tokens); + } + + self.tokens + } + + fn struct_name(&self) -> &Ident { + &self.item.ident + } + + fn attr_name(&self) -> LitStr { + let struct_name = self.struct_name(); + let name = struct_name.to_string().to_snake_case(); + LitStr::new(&name, struct_name.span()) + } + + fn expand_from_attributes_method(&mut self) { + let struct_name = self.struct_name(); + let attr_name = self.attr_name(); + + let code = quote! { + impl #struct_name { + pub fn try_from_attributes(attrs: &[syn::Attribute]) -> syn::Result> { + use syn::spanned::Spanned; + + for attr in attrs { + if attr.path().is_ident(#attr_name) { + return Some(attr.parse_args::()).transpose() + } + } + + Ok(None) + } + + pub fn from_attributes(attrs: &[syn::Attribute]) -> syn::Result { + if let Some(attr) = Self::try_from_attributes(attrs)? { + Ok(attr) + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + &format!("missing attribute `#[{}]`", #attr_name), + )) + } + } + } + }; + self.tokens.extend(code); + } + + fn expand_parse_impl(&mut self) { + let struct_name = self.struct_name(); + let attr_name = self.attr_name(); + + let variable_declarations = self.item.fields.iter().map(|field| { + let name = &field.ident; + quote! { let mut #name = std::option::Option::None; } + }); + + let match_arms = self.item.fields.iter().map(|field| { + let field_name = get_field_name(field); + let pattern = LitStr::new(&field_name.to_string(), field.span()); + + if field_is_switch(field) { + quote! { + #pattern => { + #field_name = std::option::Option::Some(()); + } + } + } else { + quote! { + #pattern => { + input.parse::()?; + #field_name = std::option::Option::Some(input.parse()?); + } + } + } + }); + + let unwrap_mandatory_fields = self + .item + .fields + .iter() + .filter(|field| !field_is_optional(field)) + .map(|field| { + let field_name = get_field_name(field); + let arg_name = LitStr::new(&field_name.to_string(), field.span()); + + quote! { + let #field_name = if let std::option::Option::Some(#field_name) = #field_name { + #field_name + } else { + return syn::Result::Err( + input.error( + &format!("`#[{}]` is missing `{}` argument", #attr_name, #arg_name), + ) + ); + }; + } + }); + + let set_fields = self.item.fields.iter().map(|field| { + let field_name = get_field_name(field); + quote! { #field_name, } + }); + + let mut supported_args = self + .item + .fields + .iter() + .map(get_field_name) + .map(|field_name| format!("`{}`", field_name)) + .collect::>(); + supported_args.sort_unstable(); + let supported_args = supported_args.join(", "); + + let code = quote! { + impl syn::parse::Parse for #struct_name { + #[allow(unreachable_code, unused_imports, unused_variables)] + fn parse(input: syn::parse::ParseStream) -> syn::Result { + #(#variable_declarations)* + + while !input.is_empty() { + let bae_attr_ident = input.parse::()?; + + match &*bae_attr_ident.to_string() { + #(#match_arms)* + other => { + return syn::Result::Err( + syn::Error::new( + bae_attr_ident.span(), + &format!( + "`#[{}]` got unknown `{}` argument. Supported arguments are {}", + #attr_name, + other, + #supported_args, + ), + ) + ); + } + } + + input.parse::().ok(); + } + + #(#unwrap_mandatory_fields)* + + syn::Result::Ok(Self { #(#set_fields)* }) + } + } + }; + self.tokens.extend(code); + } +} + +fn get_field_name(field: &Field) -> &Ident { + field + .ident + .as_ref() + .unwrap_or_else(|| abort!(field.span(), "Field without a name")) +} + +fn field_is_optional(field: &Field) -> bool { + let type_path = if let Type::Path(type_path) = &field.ty { + type_path + } else { + return false; + }; + + let ident = &type_path + .path + .segments + .last() + .unwrap_or_else(|| abort!(field.span(), "Empty type path")) + .ident; + + ident == "Option" +} + +fn field_is_switch(field: &Field) -> bool { + let unit_type = syn::parse_str::("()").unwrap(); + inner_type(&field.ty) == Some(&unit_type) +} + +fn inner_type(ty: &Type) -> Option<&Type> { + let type_path = if let Type::Path(type_path) = ty { + type_path + } else { + return None; + }; + + let ty_args = &type_path + .path + .segments + .last() + .unwrap_or_else(|| abort!(ty.span(), "Empty type path")) + .arguments; + + let ty_args = if let PathArguments::AngleBracketed(ty_args) = ty_args { + ty_args + } else { + return None; + }; + + let generic_arg = &ty_args + .args + .last() + .unwrap_or_else(|| abort!(ty_args.span(), "Empty generic argument")); + + let ty = if let GenericArgument::Type(ty) = generic_arg { + ty + } else { + return None; + }; + + Some(ty) +} + +#[cfg(test)] +mod test { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_ui() { + let t = trybuild::TestCases::new(); + t.pass("tests/compile_pass/*.rs"); + t.compile_fail("tests/compile_fail/*.rs"); + } +} From 09b95bb045881e9bc6151626f859dd9860bb57d7 Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Tue, 4 Jul 2023 18:47:43 +0800 Subject: [PATCH 2/5] doc update --- sea-orm-macros/sea-bae/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sea-orm-macros/sea-bae/src/lib.rs b/sea-orm-macros/sea-bae/src/lib.rs index 5da953d6b..6385ce7d8 100644 --- a/sea-orm-macros/sea-bae/src/lib.rs +++ b/sea-orm-macros/sea-bae/src/lib.rs @@ -3,13 +3,12 @@ //! simpler API. //! //! ```rust -//! use bae2::FromAttributes; +//! use bae::FromAttributes; //! //! #[derive( //! Debug, //! Eq, //! PartialEq, -//! //! // This will add two functions: //! // ``` //! // fn from_attributes(attrs: &[syn::Attribute]) -> Result @@ -67,7 +66,10 @@ //! Some(syn::parse_str::("OtherType").unwrap()) //! ); //! -//! assert_eq!(my_attr.mandatory_ident, syn::parse_str::("foo").unwrap()); +//! assert_eq!( +//! my_attr.mandatory_ident, +//! syn::parse_str::("foo").unwrap() +//! ); //! //! assert_eq!(my_attr.switch.is_some(), true); //! From 836b34bb9b86a3e935cff392a5744125c22425ba Mon Sep 17 00:00:00 2001 From: Yiu Tin Cheung Ivan Date: Wed, 5 Jul 2023 10:37:24 +0800 Subject: [PATCH 3/5] fix doc --- sea-orm-macros/sea-bae/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sea-orm-macros/sea-bae/src/lib.rs b/sea-orm-macros/sea-bae/src/lib.rs index 6385ce7d8..6a7ea7b4f 100644 --- a/sea-orm-macros/sea-bae/src/lib.rs +++ b/sea-orm-macros/sea-bae/src/lib.rs @@ -1,9 +1,11 @@ //! `bae` is a crate for proc macro authors, which simplifies parsing of attributes. It is //! heavily inspired by [`darling`](https://crates.io/crates/darling) but has a significantly //! simpler API. +//! +//! sea-bae is a fork of the original bae crate, for use in [`sea-orm`](https://crates.io/crates/sea-orm). //! //! ```rust -//! use bae::FromAttributes; +//! use sea_bae::FromAttributes; //! //! #[derive( //! Debug, From 730ceca94182b9c1757aaefc3a90eea45c3785c0 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 10 Jul 2023 15:24:27 +0800 Subject: [PATCH 4/5] Remove sea-bae from the workspace --- sea-orm-macros/sea-bae/Cargo.toml | 60 ---- sea-orm-macros/sea-bae/Cargo.toml.orig | 30 -- sea-orm-macros/sea-bae/src/lib.rs | 371 ------------------------- 3 files changed, 461 deletions(-) delete mode 100644 sea-orm-macros/sea-bae/Cargo.toml delete mode 100644 sea-orm-macros/sea-bae/Cargo.toml.orig delete mode 100644 sea-orm-macros/sea-bae/src/lib.rs diff --git a/sea-orm-macros/sea-bae/Cargo.toml b/sea-orm-macros/sea-bae/Cargo.toml deleted file mode 100644 index 25a486733..000000000 --- a/sea-orm-macros/sea-bae/Cargo.toml +++ /dev/null @@ -1,60 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -name = "sea-bae" -version = "1.0.0" -authors = [ - "David Pedersen ", - "Naoki Ikeguchi ", -] -description = "A Rust proc-macro attribute parser" -homepage = "https://github.com/siketyan/bae2" -documentation = "https://docs.rs/bae2" -readme = "README.md" -keywords = [ - "macro", - "derive", - "attributes", - "proc", -] -categories = ["development-tools::procedural-macro-helpers"] -license = "MIT" -repository = "https://github.com/siketyan/bae2.git" - -[lib] -name = "sea_bae" -path = "src/lib.rs" -proc-macro = true - -[dependencies.heck] -version = "0.4.1" - -[dependencies.proc-macro-error] -version = "1" - -[dependencies.proc-macro2] -version = "1" - -[dependencies.quote] -version = "1" - -[dependencies.syn] -version = "2" -features = [ - "full", - "extra-traits", -] - -[dev-dependencies.trybuild] -version = "1" - diff --git a/sea-orm-macros/sea-bae/Cargo.toml.orig b/sea-orm-macros/sea-bae/Cargo.toml.orig deleted file mode 100644 index 9af5a23c8..000000000 --- a/sea-orm-macros/sea-bae/Cargo.toml.orig +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "bae2" -description = "A Rust proc-macro attribute parser" -version = "1.0.0" -edition = "2021" -readme = "README.md" -license = "MIT" -homepage = "https://github.com/siketyan/bae2" -documentation = "https://docs.rs/bae2" -repository = "https://github.com/siketyan/bae2.git" -authors = [ - "David Pedersen ", - "Naoki Ikeguchi ", -] -keywords = ["macro", "derive", "attributes", "proc"] -categories = ["development-tools::procedural-macro-helpers"] - -[dependencies] -syn = { version = "2", features = ["full", "extra-traits"] } -quote = "1" -proc-macro2 = "1" -proc-macro-error = "1" -heck = "0.4.1" - -[dev_dependencies] -trybuild = "1" - -[lib] -proc-macro = true -path = "src/lib.rs" diff --git a/sea-orm-macros/sea-bae/src/lib.rs b/sea-orm-macros/sea-bae/src/lib.rs deleted file mode 100644 index 6a7ea7b4f..000000000 --- a/sea-orm-macros/sea-bae/src/lib.rs +++ /dev/null @@ -1,371 +0,0 @@ -//! `bae` is a crate for proc macro authors, which simplifies parsing of attributes. It is -//! heavily inspired by [`darling`](https://crates.io/crates/darling) but has a significantly -//! simpler API. -//! -//! sea-bae is a fork of the original bae crate, for use in [`sea-orm`](https://crates.io/crates/sea-orm). -//! -//! ```rust -//! use sea_bae::FromAttributes; -//! -//! #[derive( -//! Debug, -//! Eq, -//! PartialEq, -//! // This will add two functions: -//! // ``` -//! // fn from_attributes(attrs: &[syn::Attribute]) -> Result -//! // fn try_from_attributes(attrs: &[syn::Attribute]) -> Result, syn::Error> -//! // ``` -//! // -//! // `try_from_attributes` returns `Ok(None)` if the attribute is missing, `Ok(Some(_))` if -//! // its there and is valid, `Err(_)` otherwise. -//! FromAttributes, -//! )] -//! pub struct MyAttr { -//! // Anything that implements `syn::parse::Parse` is supported. -//! mandatory_type: syn::Type, -//! mandatory_ident: syn::Ident, -//! -//! // Fields wrapped in `Option` are optional and default to `None` if -//! // not specified in the attribute. -//! optional_missing: Option, -//! optional_given: Option, -//! -//! // A "switch" is something that doesn't take arguments. -//! // All fields with type `Option<()>` are considered swiches. -//! // They default to `None`. -//! switch: Option<()>, -//! } -//! -//! // `MyAttr` is now equipped to parse attributes named `my_attr`. For example: -//! // -//! // #[my_attr( -//! // switch, -//! // mandatory_ident = foo, -//! // mandatory_type = SomeType, -//! // optional_given = OtherType, -//! // )] -//! // struct Foo { -//! // ... -//! // } -//! -//! // the input and output type would normally be `proc_macro::TokenStream` but those -//! // types cannot be used outside the compiler itself. -//! fn my_proc_macro(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream { -//! let item_struct = syn::parse2::(input).unwrap(); -//! -//! let my_attr = MyAttr::from_attributes(&item_struct.attrs).unwrap(); -//! -//! assert_eq!( -//! my_attr.mandatory_type, -//! syn::parse_str::("SomeType").unwrap() -//! ); -//! -//! assert_eq!(my_attr.optional_missing, None); -//! -//! assert_eq!( -//! my_attr.optional_given, -//! Some(syn::parse_str::("OtherType").unwrap()) -//! ); -//! -//! assert_eq!( -//! my_attr.mandatory_ident, -//! syn::parse_str::("foo").unwrap() -//! ); -//! -//! assert_eq!(my_attr.switch.is_some(), true); -//! -//! // ... -//! # -//! # quote::quote! {} -//! } -//! # -//! # fn main() { -//! # let code = quote::quote! { -//! # #[other_random_attr] -//! # #[my_attr( -//! # switch, -//! # mandatory_ident = foo, -//! # mandatory_type = SomeType, -//! # optional_given = OtherType, -//! # )] -//! # struct Foo; -//! # }; -//! # my_proc_macro(code); -//! # } -//! ``` - -#![doc(html_root_url = "https://docs.rs/bae/0.1.7")] -#![allow(clippy::let_and_return)] -#![deny( - unused_variables, - dead_code, - unused_must_use, - unused_imports, - missing_docs -)] - -extern crate proc_macro; - -use heck::ToSnakeCase; -use proc_macro2::TokenStream; -use proc_macro_error::*; -use quote::*; -use syn::{spanned::Spanned, *}; - -/// See root module docs for more info. -#[proc_macro_derive(FromAttributes, attributes())] -#[proc_macro_error] -pub fn from_attributes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let item = parse_macro_input!(input as ItemStruct); - FromAttributes::new(item).expand().into() -} - -#[derive(Debug)] -struct FromAttributes { - item: ItemStruct, - tokens: TokenStream, -} - -impl FromAttributes { - fn new(item: ItemStruct) -> Self { - Self { - item, - tokens: TokenStream::new(), - } - } - - fn expand(mut self) -> TokenStream { - self.expand_from_attributes_method(); - self.expand_parse_impl(); - - if std::env::var("BAE_DEBUG").is_ok() { - eprintln!("{}", self.tokens); - } - - self.tokens - } - - fn struct_name(&self) -> &Ident { - &self.item.ident - } - - fn attr_name(&self) -> LitStr { - let struct_name = self.struct_name(); - let name = struct_name.to_string().to_snake_case(); - LitStr::new(&name, struct_name.span()) - } - - fn expand_from_attributes_method(&mut self) { - let struct_name = self.struct_name(); - let attr_name = self.attr_name(); - - let code = quote! { - impl #struct_name { - pub fn try_from_attributes(attrs: &[syn::Attribute]) -> syn::Result> { - use syn::spanned::Spanned; - - for attr in attrs { - if attr.path().is_ident(#attr_name) { - return Some(attr.parse_args::()).transpose() - } - } - - Ok(None) - } - - pub fn from_attributes(attrs: &[syn::Attribute]) -> syn::Result { - if let Some(attr) = Self::try_from_attributes(attrs)? { - Ok(attr) - } else { - Err(syn::Error::new( - proc_macro2::Span::call_site(), - &format!("missing attribute `#[{}]`", #attr_name), - )) - } - } - } - }; - self.tokens.extend(code); - } - - fn expand_parse_impl(&mut self) { - let struct_name = self.struct_name(); - let attr_name = self.attr_name(); - - let variable_declarations = self.item.fields.iter().map(|field| { - let name = &field.ident; - quote! { let mut #name = std::option::Option::None; } - }); - - let match_arms = self.item.fields.iter().map(|field| { - let field_name = get_field_name(field); - let pattern = LitStr::new(&field_name.to_string(), field.span()); - - if field_is_switch(field) { - quote! { - #pattern => { - #field_name = std::option::Option::Some(()); - } - } - } else { - quote! { - #pattern => { - input.parse::()?; - #field_name = std::option::Option::Some(input.parse()?); - } - } - } - }); - - let unwrap_mandatory_fields = self - .item - .fields - .iter() - .filter(|field| !field_is_optional(field)) - .map(|field| { - let field_name = get_field_name(field); - let arg_name = LitStr::new(&field_name.to_string(), field.span()); - - quote! { - let #field_name = if let std::option::Option::Some(#field_name) = #field_name { - #field_name - } else { - return syn::Result::Err( - input.error( - &format!("`#[{}]` is missing `{}` argument", #attr_name, #arg_name), - ) - ); - }; - } - }); - - let set_fields = self.item.fields.iter().map(|field| { - let field_name = get_field_name(field); - quote! { #field_name, } - }); - - let mut supported_args = self - .item - .fields - .iter() - .map(get_field_name) - .map(|field_name| format!("`{}`", field_name)) - .collect::>(); - supported_args.sort_unstable(); - let supported_args = supported_args.join(", "); - - let code = quote! { - impl syn::parse::Parse for #struct_name { - #[allow(unreachable_code, unused_imports, unused_variables)] - fn parse(input: syn::parse::ParseStream) -> syn::Result { - #(#variable_declarations)* - - while !input.is_empty() { - let bae_attr_ident = input.parse::()?; - - match &*bae_attr_ident.to_string() { - #(#match_arms)* - other => { - return syn::Result::Err( - syn::Error::new( - bae_attr_ident.span(), - &format!( - "`#[{}]` got unknown `{}` argument. Supported arguments are {}", - #attr_name, - other, - #supported_args, - ), - ) - ); - } - } - - input.parse::().ok(); - } - - #(#unwrap_mandatory_fields)* - - syn::Result::Ok(Self { #(#set_fields)* }) - } - } - }; - self.tokens.extend(code); - } -} - -fn get_field_name(field: &Field) -> &Ident { - field - .ident - .as_ref() - .unwrap_or_else(|| abort!(field.span(), "Field without a name")) -} - -fn field_is_optional(field: &Field) -> bool { - let type_path = if let Type::Path(type_path) = &field.ty { - type_path - } else { - return false; - }; - - let ident = &type_path - .path - .segments - .last() - .unwrap_or_else(|| abort!(field.span(), "Empty type path")) - .ident; - - ident == "Option" -} - -fn field_is_switch(field: &Field) -> bool { - let unit_type = syn::parse_str::("()").unwrap(); - inner_type(&field.ty) == Some(&unit_type) -} - -fn inner_type(ty: &Type) -> Option<&Type> { - let type_path = if let Type::Path(type_path) = ty { - type_path - } else { - return None; - }; - - let ty_args = &type_path - .path - .segments - .last() - .unwrap_or_else(|| abort!(ty.span(), "Empty type path")) - .arguments; - - let ty_args = if let PathArguments::AngleBracketed(ty_args) = ty_args { - ty_args - } else { - return None; - }; - - let generic_arg = &ty_args - .args - .last() - .unwrap_or_else(|| abort!(ty_args.span(), "Empty generic argument")); - - let ty = if let GenericArgument::Type(ty) = generic_arg { - ty - } else { - return None; - }; - - Some(ty) -} - -#[cfg(test)] -mod test { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_ui() { - let t = trybuild::TestCases::new(); - t.pass("tests/compile_pass/*.rs"); - t.compile_fail("tests/compile_fail/*.rs"); - } -} From fcaf8864d4e76c8cf71c169b8a99207cbd521352 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Mon, 10 Jul 2023 15:24:38 +0800 Subject: [PATCH 5/5] Use sea-bae --- Cargo.toml | 2 +- sea-orm-macros/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad5063236..037de27a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "sea-orm-macros", "sea-orm-codegen" , "sea-orm-macros/sea-bae"] +members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 08c5c5f59..49b71a38b 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" proc-macro = true [dependencies] -bae = { version = "1", path = "sea-bae", package = "sea-bae", default-features = false, optional = true } +bae = { version = "0.2", package = "sea-bae", default-features = false, optional = true } syn = { version = "2", default-features = false, features = ["parsing", "proc-macro", "derive", "printing"] } quote = { version = "1", default-features = false } heck = { version = "0.4", default-features = false }