Skip to content

Commit dba9212

Browse files
committed
Fix compile fail message for mixed #[diesel(embed, serialize_as = _)]
1 parent 24fcf3c commit dba9212

File tree

7 files changed

+140
-37
lines changed

7 files changed

+140
-37
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: `#[diesel(embed)]` cannot be combined with `#[diesel(serialize_as)]`
2-
--> $DIR/embed_and_serialize_as_cannot_be_mixed.rs:22:36
2+
--> tests/fail/derive/embed_and_serialize_as_cannot_be_mixed.rs:22:13
33
|
44
22 | #[diesel(embed, serialize_as = SomeType)]
5-
| ^^^^^^^^
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

diesel_derives/src/attrs.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter};
22

33
use proc_macro2::{Span, TokenStream};
44
use proc_macro_error::ResultExt;
5+
use quote::spanned::Spanned;
56
use quote::ToTokens;
67
use syn::parse::discouraged::Speculative;
78
use syn::parse::{Parse, ParseStream, Parser, Result};
@@ -13,6 +14,12 @@ use deprecated::ParseDeprecated;
1314
use parsers::{BelongsTo, MysqlType, PostgresType, SqliteType};
1415
use util::{parse_eq, parse_paren, unknown_attribute};
1516

17+
pub struct AttributeSpanWrapper<T> {
18+
pub item: T,
19+
pub attribute_span: Span,
20+
pub ident_span: Span,
21+
}
22+
1623
pub enum FieldAttr {
1724
Embed(Ident),
1825

@@ -96,6 +103,18 @@ impl Parse for FieldAttr {
96103
}
97104
}
98105

106+
impl Spanned for FieldAttr {
107+
fn __span(&self) -> Span {
108+
match self {
109+
FieldAttr::Embed(ident)
110+
| FieldAttr::ColumnName(ident, _)
111+
| FieldAttr::SqlType(ident, _)
112+
| FieldAttr::SerializeAs(ident, _)
113+
| FieldAttr::DeserializeAs(ident, _) => ident.span(),
114+
}
115+
}
116+
}
117+
99118
#[allow(clippy::large_enum_variant)]
100119
pub enum StructAttr {
101120
Aggregate(Ident),
@@ -146,15 +165,46 @@ impl Parse for StructAttr {
146165
}
147166
}
148167

149-
pub fn parse_attributes<T: Parse + ParseDeprecated>(attrs: &[Attribute]) -> Vec<T> {
168+
impl Spanned for StructAttr {
169+
fn __span(&self) -> Span {
170+
match self {
171+
StructAttr::Aggregate(ident)
172+
| StructAttr::NotSized(ident)
173+
| StructAttr::ForeignDerive(ident)
174+
| StructAttr::TableName(ident, _)
175+
| StructAttr::SqlType(ident, _)
176+
| StructAttr::TreatNoneAsDefaultValue(ident, _)
177+
| StructAttr::TreatNoneAsNull(ident, _)
178+
| StructAttr::BelongsTo(ident, _)
179+
| StructAttr::MysqlType(ident, _)
180+
| StructAttr::SqliteType(ident, _)
181+
| StructAttr::PostgresType(ident, _)
182+
| StructAttr::PrimaryKey(ident, _) => ident.span(),
183+
}
184+
}
185+
}
186+
187+
pub fn parse_attributes<T>(attrs: &[Attribute]) -> Vec<AttributeSpanWrapper<T>>
188+
where
189+
T: Parse + ParseDeprecated + Spanned,
190+
{
191+
use syn::spanned::Spanned;
192+
150193
attrs
151194
.iter()
152195
.flat_map(|attr| {
153196
if attr.path.is_ident("diesel") {
154197
attr.parse_args_with(Punctuated::<T, Comma>::parse_terminated)
155198
.unwrap_or_abort()
199+
.into_iter()
200+
.map(|a| AttributeSpanWrapper {
201+
ident_span: a.span(),
202+
item: a,
203+
attribute_span: attr.tokens.span(),
204+
})
205+
.collect::<Vec<_>>()
156206
} else {
157-
let mut p = Punctuated::new();
207+
let mut p = Vec::new();
158208
let Attribute { path, tokens, .. } = attr;
159209
let ident = path.get_ident().map(|f| f.to_string());
160210

@@ -166,10 +216,13 @@ pub fn parse_attributes<T: Parse + ParseDeprecated>(attrs: &[Attribute]) -> Vec<
166216
let value = Parser::parse(T::parse_deprecated, ts).unwrap_or_abort();
167217

168218
if let Some(value) = value {
169-
p.push_value(value);
219+
p.push(AttributeSpanWrapper {
220+
ident_span: value.span(),
221+
item: value,
222+
attribute_span: attr.tokens.span(),
223+
});
170224
}
171225
}
172-
173226
p
174227
}
175228
})

diesel_derives/src/field.rs

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ use syn::{Field as SynField, Ident, Index, Type};
55

66
use attrs::{parse_attributes, FieldAttr, SqlIdentifier};
77

8+
use crate::attrs::AttributeSpanWrapper;
9+
810
pub struct Field {
911
pub ty: Type,
1012
pub span: Span,
1113
pub name: FieldName,
12-
column_name: Option<SqlIdentifier>,
13-
pub sql_type: Option<Type>,
14-
pub serialize_as: Option<Type>,
15-
pub deserialize_as: Option<Type>,
16-
pub embed: bool,
14+
column_name: Option<AttributeSpanWrapper<SqlIdentifier>>,
15+
pub sql_type: Option<AttributeSpanWrapper<Type>>,
16+
pub serialize_as: Option<AttributeSpanWrapper<Type>>,
17+
pub deserialize_as: Option<AttributeSpanWrapper<Type>>,
18+
pub embed: Option<AttributeSpanWrapper<bool>>,
1719
}
1820

1921
impl Field {
@@ -26,15 +28,47 @@ impl Field {
2628
let mut sql_type = None;
2729
let mut serialize_as = None;
2830
let mut deserialize_as = None;
29-
let mut embed = false;
31+
let mut embed = None;
3032

3133
for attr in parse_attributes(attrs) {
32-
match attr {
33-
FieldAttr::ColumnName(_, value) => column_name = Some(value),
34-
FieldAttr::SqlType(_, value) => sql_type = Some(value),
35-
FieldAttr::SerializeAs(_, value) => serialize_as = Some(value),
36-
FieldAttr::DeserializeAs(_, value) => deserialize_as = Some(value),
37-
FieldAttr::Embed(_) => embed = true,
34+
let attribute_span = attr.attribute_span;
35+
let ident_span = attr.ident_span;
36+
match attr.item {
37+
FieldAttr::ColumnName(_, value) => {
38+
column_name = Some(AttributeSpanWrapper {
39+
item: value,
40+
attribute_span,
41+
ident_span,
42+
})
43+
}
44+
FieldAttr::SqlType(_, value) => {
45+
sql_type = Some(AttributeSpanWrapper {
46+
item: value,
47+
attribute_span,
48+
ident_span,
49+
})
50+
}
51+
FieldAttr::SerializeAs(_, value) => {
52+
serialize_as = Some(AttributeSpanWrapper {
53+
item: value,
54+
attribute_span,
55+
ident_span,
56+
})
57+
}
58+
FieldAttr::DeserializeAs(_, value) => {
59+
deserialize_as = Some(AttributeSpanWrapper {
60+
item: value,
61+
attribute_span,
62+
ident_span,
63+
})
64+
}
65+
FieldAttr::Embed(_) => {
66+
embed = Some(AttributeSpanWrapper {
67+
item: true,
68+
attribute_span,
69+
ident_span,
70+
})
71+
}
3872
}
3973
}
4074

@@ -43,9 +77,14 @@ impl Field {
4377
None => FieldName::Unnamed(index.into()),
4478
};
4579

80+
let span = match name {
81+
FieldName::Named(ref ident) => ident.span(),
82+
FieldName::Unnamed(_) => ty.span(),
83+
};
84+
4685
Self {
4786
ty: ty.clone(),
48-
span: field.span(),
87+
span,
4988
name,
5089
column_name,
5190
sql_type,
@@ -56,24 +95,31 @@ impl Field {
5695
}
5796

5897
pub fn column_name(&self) -> SqlIdentifier {
59-
self.column_name.clone().unwrap_or_else(|| match self.name {
60-
FieldName::Named(ref x) => x.into(),
61-
FieldName::Unnamed(ref x) => {
62-
abort!(
98+
self.column_name
99+
.as_ref()
100+
.map(|a| a.item.clone())
101+
.unwrap_or_else(|| match self.name {
102+
FieldName::Named(ref x) => x.into(),
103+
FieldName::Unnamed(ref x) => {
104+
abort!(
63105
x,
64106
"All fields of tuple structs must be annotated with `#[diesel(column_name)]`"
65107
);
66-
}
67-
})
108+
}
109+
})
68110
}
69111

70112
pub fn ty_for_deserialize(&self) -> &Type {
71-
if let Some(value) = &self.deserialize_as {
113+
if let Some(AttributeSpanWrapper { item: value, .. }) = &self.deserialize_as {
72114
value
73115
} else {
74116
&self.ty
75117
}
76118
}
119+
120+
pub(crate) fn embed(&self) -> bool {
121+
self.embed.as_ref().map(|a| a.item).unwrap_or(false)
122+
}
77123
}
78124

79125
pub enum FieldName {

diesel_derives/src/insertable.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use field::Field;
55
use model::Model;
66
use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};
77

8+
use crate::attrs::AttributeSpanWrapper;
9+
810
pub fn derive(item: DeriveInput) -> TokenStream {
911
let model = Model::from_item(&item, false);
1012

@@ -25,7 +27,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
2527
let mut ref_field_assign = Vec::with_capacity(model.fields().len());
2628

2729
for field in model.fields() {
28-
match (field.serialize_as.as_ref(), field.embed) {
30+
match (field.serialize_as.as_ref(), field.embed()) {
2931
(None, true) => {
3032
direct_field_ty.push(field_ty_embed(field, None));
3133
direct_field_assign.push(field_expr_embed(field, None));
@@ -58,7 +60,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
5860
treat_none_as_default_value,
5961
));
6062
}
61-
(Some(ty), false) => {
63+
(Some(AttributeSpanWrapper { item: ty, .. }), false) => {
6264
direct_field_ty.push(field_ty_serialize_as(
6365
field,
6466
table_name,
@@ -74,9 +76,9 @@ pub fn derive(item: DeriveInput) -> TokenStream {
7476

7577
generate_borrowed_insert = false; // as soon as we hit one field with #[diesel(serialize_as)] there is no point in generating the impl of Insertable for borrowed structs
7678
}
77-
(Some(ty), true) => {
79+
(Some(AttributeSpanWrapper { attribute_span, .. }), true) => {
7880
abort!(
79-
ty,
81+
attribute_span,
8082
"`#[diesel(embed)]` cannot be combined with `#[diesel(serialize_as)]`"
8183
)
8284
}

diesel_derives/src/model.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl Model {
6363
let mut postgres_type = None;
6464

6565
for attr in parse_attributes(attrs) {
66-
match attr {
66+
match attr.item {
6767
StructAttr::SqlType(_, value) => sql_types.push(value),
6868
StructAttr::TableName(_, value) => table_name = Some(value),
6969
StructAttr::PrimaryKey(_, keys) => {

diesel_derives/src/queryable_by_name.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use field::{Field, FieldName};
55
use model::Model;
66
use util::wrap_in_dummy_mod;
77

8+
use crate::attrs::AttributeSpanWrapper;
9+
810
pub fn derive(item: DeriveInput) -> TokenStream {
911
let model = Model::from_item(&item, false);
1012

@@ -15,7 +17,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
1517
let initial_field_expr = model.fields().iter().map(|f| {
1618
let field_ty = &f.ty;
1719

18-
if f.embed {
20+
if f.embed() {
1921
quote!(<#field_ty as QueryableByName<__DB>>::build(row)?)
2022
} else {
2123
let deserialize_ty = f.ty_for_deserialize();
@@ -39,7 +41,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
3941
for field in model.fields() {
4042
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
4143
let field_ty = field.ty_for_deserialize();
42-
if field.embed {
44+
if field.embed() {
4345
where_clause
4446
.predicates
4547
.push(parse_quote!(#field_ty: QueryableByName<__DB>));
@@ -88,7 +90,7 @@ fn sql_type(field: &Field, model: &Model) -> Type {
8890
let table_name = model.table_name();
8991

9092
match field.sql_type {
91-
Some(ref st) => st.clone(),
93+
Some(AttributeSpanWrapper { item: ref st, .. }) => st.clone(),
9294
None => {
9395
if model.has_table_name_attribute() {
9496
let column_name = field.column_name();

diesel_derives/src/selectable.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
1515
.params
1616
.push(parse_quote!(__DB: diesel::backend::Backend));
1717

18-
for embed_field in model.fields().iter().filter(|f| f.embed) {
18+
for embed_field in model.fields().iter().filter(|f| f.embed()) {
1919
let embed_ty = &embed_field.ty;
2020
generics
2121
.where_clause
@@ -48,7 +48,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
4848
}
4949

5050
fn field_column_ty(field: &Field, model: &Model) -> TokenStream {
51-
if field.embed {
51+
if field.embed() {
5252
let embed_ty = &field.ty;
5353
quote!(<#embed_ty as Selectable<__DB>>::SelectExpression)
5454
} else {
@@ -59,7 +59,7 @@ fn field_column_ty(field: &Field, model: &Model) -> TokenStream {
5959
}
6060

6161
fn field_column_inst(field: &Field, model: &Model) -> TokenStream {
62-
if field.embed {
62+
if field.embed() {
6363
let embed_ty = &field.ty;
6464
quote!(<#embed_ty as Selectable<__DB>>::construct_selection())
6565
} else {

0 commit comments

Comments
 (0)