Merge pull request #857 from malobre/feature/derive-transparent

Derive transparent
This commit is contained in:
Steven Fackler 2021-12-30 17:54:42 -05:00 committed by GitHub
commit 774016d5c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 251 additions and 46 deletions

View File

@ -0,0 +1,35 @@
use postgres_types::{FromSql, ToSql};
#[derive(ToSql, Debug)]
#[postgres(transparent)]
struct ToSqlTransparentStruct {
a: i32
}
#[derive(FromSql, Debug)]
#[postgres(transparent)]
struct FromSqlTransparentStruct {
a: i32
}
#[derive(ToSql, Debug)]
#[postgres(transparent)]
enum ToSqlTransparentEnum {
Foo
}
#[derive(FromSql, Debug)]
#[postgres(transparent)]
enum FromSqlTransparentEnum {
Foo
}
#[derive(ToSql, Debug)]
#[postgres(transparent)]
struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
#[derive(FromSql, Debug)]
#[postgres(transparent)]
struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
fn main() {}

View File

@ -0,0 +1,49 @@
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:4:1
|
4 | / #[postgres(transparent)]
5 | | struct ToSqlTransparentStruct {
6 | | a: i32
7 | | }
| |_^
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:10:1
|
10 | / #[postgres(transparent)]
11 | | struct FromSqlTransparentStruct {
12 | | a: i32
13 | | }
| |_^
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:16:1
|
16 | / #[postgres(transparent)]
17 | | enum ToSqlTransparentEnum {
18 | | Foo
19 | | }
| |_^
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:22:1
|
22 | / #[postgres(transparent)]
23 | | enum FromSqlTransparentEnum {
24 | | Foo
25 | | }
| |_^
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:28:1
|
28 | / #[postgres(transparent)]
29 | | struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
| |_____________________________________________________^
error: #[postgres(transparent)] may only be applied to single field tuple structs
--> src/compile-fail/invalid-transparent.rs:32:1
|
32 | / #[postgres(transparent)]
33 | | struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
| |_______________________________________________________^

View File

@ -7,6 +7,7 @@ use std::fmt;
mod composites;
mod domains;
mod enums;
mod transparent;
pub fn test_type<T, S>(conn: &mut Client, sql_type: &str, checks: &[(T, S)])
where

View File

@ -0,0 +1,18 @@
use postgres::{Client, NoTls};
use postgres_types::{FromSql, ToSql};
#[test]
fn round_trip() {
#[derive(FromSql, ToSql, Debug, PartialEq)]
#[postgres(transparent)]
struct UserId(i32);
assert_eq!(
Client::connect("user=postgres host=localhost port=5433", NoTls)
.unwrap()
.query_one("SELECT $1::integer", &[&UserId(123)])
.unwrap()
.get::<_, UserId>(0),
UserId(123)
);
}

View File

@ -6,6 +6,14 @@ use syn::Ident;
use crate::composites::Field;
use crate::enums::Variant;
pub fn transparent_body(field: &syn::Field) -> TokenStream {
let ty = &field.ty;
quote! {
<#ty as ::postgres_types::ToSql>::accepts(type_)
}
}
pub fn domain_body(name: &str, field: &syn::Field) -> TokenStream {
let ty = &field.ty;

View File

@ -11,9 +11,36 @@ use crate::overrides::Overrides;
pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
let overrides = Overrides::extract(&input.attrs)?;
if overrides.name.is_some() && overrides.transparent {
return Err(Error::new_spanned(
&input,
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
));
}
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
let (accepts_body, to_sql_body) = match input.data {
let (accepts_body, to_sql_body) = if overrides.transparent {
match input.data {
Data::Struct(DataStruct {
fields: Fields::Unnamed(ref fields),
..
}) if fields.unnamed.len() == 1 => {
let field = fields.unnamed.first().unwrap();
(
accepts::transparent_body(field),
transparent_body(&input.ident, field),
)
}
_ => {
return Err(Error::new_spanned(
input,
"#[postgres(transparent)] may only be applied to single field tuple structs",
))
}
}
} else {
match input.data {
Data::Enum(ref data) => {
let variants = data
.variants
@ -55,6 +82,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
"#[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums",
))
}
}
};
let ident = &input.ident;
@ -77,6 +105,13 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
Ok(out)
}
fn transparent_body(ident: &Ident, field: &syn::Field) -> TokenStream {
let ty = &field.ty;
quote! {
<#ty as postgres_types::FromSql>::from_sql(_type, buf).map(#ident)
}
}
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
let variant_names = variants.iter().map(|v| &v.name);
let idents = iter::repeat(ident);

View File

@ -4,6 +4,7 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::parse_macro_input;
mod accepts;
mod composites;
@ -14,7 +15,8 @@ mod tosql;
#[proc_macro_derive(ToSql, attributes(postgres))]
pub fn derive_tosql(input: TokenStream) -> TokenStream {
let input = syn::parse(input).unwrap();
let input = parse_macro_input!(input);
tosql::expand_derive_tosql(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
@ -22,7 +24,8 @@ pub fn derive_tosql(input: TokenStream) -> TokenStream {
#[proc_macro_derive(FromSql, attributes(postgres))]
pub fn derive_fromsql(input: TokenStream) -> TokenStream {
let input = syn::parse(input).unwrap();
let input = parse_macro_input!(input);
fromsql::expand_derive_fromsql(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()

View File

@ -2,17 +2,18 @@ use syn::{Attribute, Error, Lit, Meta, NestedMeta};
pub struct Overrides {
pub name: Option<String>,
pub transparent: bool,
}
impl Overrides {
pub fn extract(attrs: &[Attribute]) -> Result<Overrides, Error> {
let mut overrides = Overrides { name: None };
let mut overrides = Overrides {
name: None,
transparent: false,
};
for attr in attrs {
let attr = match attr.parse_meta() {
Ok(meta) => meta,
Err(_) => continue,
};
let attr = attr.parse_meta()?;
if !attr.path().is_ident("postgres") {
continue;
@ -39,7 +40,14 @@ impl Overrides {
overrides.name = Some(value);
}
bad => return Err(Error::new_spanned(bad, "expected a name-value meta item")),
NestedMeta::Meta(Meta::Path(ref path)) => {
if !path.is_ident("transparent") {
return Err(Error::new_spanned(path, "unknown override"));
}
overrides.transparent = true;
}
bad => return Err(Error::new_spanned(bad, "unknown attribute")),
}
}
}

View File

@ -11,46 +11,73 @@ use crate::overrides::Overrides;
pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
let overrides = Overrides::extract(&input.attrs)?;
if overrides.name.is_some() && overrides.transparent {
return Err(Error::new_spanned(
&input,
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
));
}
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
let (accepts_body, to_sql_body) = match input.data {
Data::Enum(ref data) => {
let variants = data
.variants
.iter()
.map(Variant::parse)
.collect::<Result<Vec<_>, _>>()?;
(
accepts::enum_body(&name, &variants),
enum_body(&input.ident, &variants),
)
let (accepts_body, to_sql_body) = if overrides.transparent {
match input.data {
Data::Struct(DataStruct {
fields: Fields::Unnamed(ref fields),
..
}) if fields.unnamed.len() == 1 => {
let field = fields.unnamed.first().unwrap();
(accepts::transparent_body(field), transparent_body())
}
_ => {
return Err(Error::new_spanned(
input,
"#[postgres(transparent)] may only be applied to single field tuple structs",
));
}
}
Data::Struct(DataStruct {
fields: Fields::Unnamed(ref fields),
..
}) if fields.unnamed.len() == 1 => {
let field = fields.unnamed.first().unwrap();
(accepts::domain_body(&name, field), domain_body())
}
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let fields = fields
.named
.iter()
.map(Field::parse)
.collect::<Result<Vec<_>, _>>()?;
(
accepts::composite_body(&name, "ToSql", &fields),
composite_body(&fields),
)
}
_ => {
return Err(Error::new_spanned(
input,
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
));
} else {
match input.data {
Data::Enum(ref data) => {
let variants = data
.variants
.iter()
.map(Variant::parse)
.collect::<Result<Vec<_>, _>>()?;
(
accepts::enum_body(&name, &variants),
enum_body(&input.ident, &variants),
)
}
Data::Struct(DataStruct {
fields: Fields::Unnamed(ref fields),
..
}) if fields.unnamed.len() == 1 => {
let field = fields.unnamed.first().unwrap();
(accepts::domain_body(&name, field), domain_body())
}
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let fields = fields
.named
.iter()
.map(Field::parse)
.collect::<Result<Vec<_>, _>>()?;
(
accepts::composite_body(&name, "ToSql", &fields),
composite_body(&fields),
)
}
_ => {
return Err(Error::new_spanned(
input,
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
));
}
}
};
@ -78,6 +105,12 @@ pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
Ok(out)
}
fn transparent_body() -> TokenStream {
quote! {
postgres_types::ToSql::to_sql(&self.0, _type, buf)
}
}
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
let idents = iter::repeat(ident);
let variant_idents = variants.iter().map(|v| &v.ident);

View File

@ -55,6 +55,21 @@
//! struct SessionId(Vec<u8>);
//! ```
//!
//! ## Newtypes
//!
//! The `#[postgres(transparent)]` attribute can be used on a single-field tuple struct to create a
//! Rust-only wrapper type that will use the [`ToSql`] & [`FromSql`] implementation of the inner
//! value :
//! ```rust
//! # #[cfg(feature = "derive")]
//! use postgres_types::{ToSql, FromSql};
//!
//! # #[cfg(feature = "derive")]
//! #[derive(Debug, ToSql, FromSql)]
//! #[postgres(transparent)]
//! struct UserId(i32);
//! ```
//!
//! ## Composites
//!
//! Postgres composite types correspond to structs in Rust: