• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use internals::ast::{Container, Data, Field, Style};
2 use internals::attr::{Identifier, TagType};
3 use internals::{ungroup, Ctxt, Derive};
4 use syn::{Member, Type};
5 
6 /// Cross-cutting checks that require looking at more than a single attrs
7 /// object. Simpler checks should happen when parsing and building the attrs.
check(cx: &Ctxt, cont: &mut Container, derive: Derive)8 pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) {
9     check_getter(cx, cont);
10     check_flatten(cx, cont);
11     check_identifier(cx, cont);
12     check_variant_skip_attrs(cx, cont);
13     check_internal_tag_field_name_conflict(cx, cont);
14     check_adjacent_tag_conflict(cx, cont);
15     check_transparent(cx, cont, derive);
16     check_from_and_try_from(cx, cont);
17 }
18 
19 /// Getters are only allowed inside structs (not enums) with the `remote`
20 /// attribute.
check_getter(cx: &Ctxt, cont: &Container)21 fn check_getter(cx: &Ctxt, cont: &Container) {
22     match cont.data {
23         Data::Enum(_) => {
24             if cont.data.has_getter() {
25                 cx.error_spanned_by(
26                     cont.original,
27                     "#[serde(getter = \"...\")] is not allowed in an enum",
28                 );
29             }
30         }
31         Data::Struct(_, _) => {
32             if cont.data.has_getter() && cont.attrs.remote().is_none() {
33                 cx.error_spanned_by(
34                     cont.original,
35                     "#[serde(getter = \"...\")] can only be used in structs that have #[serde(remote = \"...\")]",
36                 );
37             }
38         }
39     }
40 }
41 
42 /// Flattening has some restrictions we can test.
check_flatten(cx: &Ctxt, cont: &Container)43 fn check_flatten(cx: &Ctxt, cont: &Container) {
44     match &cont.data {
45         Data::Enum(variants) => {
46             for variant in variants {
47                 for field in &variant.fields {
48                     check_flatten_field(cx, variant.style, field);
49                 }
50             }
51         }
52         Data::Struct(style, fields) => {
53             for field in fields {
54                 check_flatten_field(cx, *style, field);
55             }
56         }
57     }
58 }
59 
check_flatten_field(cx: &Ctxt, style: Style, field: &Field)60 fn check_flatten_field(cx: &Ctxt, style: Style, field: &Field) {
61     if !field.attrs.flatten() {
62         return;
63     }
64     match style {
65         Style::Tuple => {
66             cx.error_spanned_by(
67                 field.original,
68                 "#[serde(flatten)] cannot be used on tuple structs",
69             );
70         }
71         Style::Newtype => {
72             cx.error_spanned_by(
73                 field.original,
74                 "#[serde(flatten)] cannot be used on newtype structs",
75             );
76         }
77         _ => {}
78     }
79 }
80 
81 /// The `other` attribute must be used at most once and it must be the last
82 /// variant of an enum.
83 ///
84 /// Inside a `variant_identifier` all variants must be unit variants. Inside a
85 /// `field_identifier` all but possibly one variant must be unit variants. The
86 /// last variant may be a newtype variant which is an implicit "other" case.
check_identifier(cx: &Ctxt, cont: &Container)87 fn check_identifier(cx: &Ctxt, cont: &Container) {
88     let variants = match &cont.data {
89         Data::Enum(variants) => variants,
90         Data::Struct(_, _) => {
91             return;
92         }
93     };
94 
95     for (i, variant) in variants.iter().enumerate() {
96         match (
97             variant.style,
98             cont.attrs.identifier(),
99             variant.attrs.other(),
100             cont.attrs.tag(),
101         ) {
102             // The `other` attribute may not be used in a variant_identifier.
103             (_, Identifier::Variant, true, _) => {
104                 cx.error_spanned_by(
105                     variant.original,
106                     "#[serde(other)] may not be used on a variant identifier",
107                 );
108             }
109 
110             // Variant with `other` attribute cannot appear in untagged enum
111             (_, Identifier::No, true, &TagType::None) => {
112                 cx.error_spanned_by(
113                     variant.original,
114                     "#[serde(other)] cannot appear on untagged enum",
115                 );
116             }
117 
118             // Variant with `other` attribute must be the last one.
119             (Style::Unit, Identifier::Field, true, _) | (Style::Unit, Identifier::No, true, _) => {
120                 if i < variants.len() - 1 {
121                     cx.error_spanned_by(
122                         variant.original,
123                         "#[serde(other)] must be on the last variant",
124                     );
125                 }
126             }
127 
128             // Variant with `other` attribute must be a unit variant.
129             (_, Identifier::Field, true, _) | (_, Identifier::No, true, _) => {
130                 cx.error_spanned_by(
131                     variant.original,
132                     "#[serde(other)] must be on a unit variant",
133                 );
134             }
135 
136             // Any sort of variant is allowed if this is not an identifier.
137             (_, Identifier::No, false, _) => {}
138 
139             // Unit variant without `other` attribute is always fine.
140             (Style::Unit, _, false, _) => {}
141 
142             // The last field is allowed to be a newtype catch-all.
143             (Style::Newtype, Identifier::Field, false, _) => {
144                 if i < variants.len() - 1 {
145                     cx.error_spanned_by(
146                         variant.original,
147                         format!("`{}` must be the last variant", variant.ident),
148                     );
149                 }
150             }
151 
152             (_, Identifier::Field, false, _) => {
153                 cx.error_spanned_by(
154                     variant.original,
155                     "#[serde(field_identifier)] may only contain unit variants",
156                 );
157             }
158 
159             (_, Identifier::Variant, false, _) => {
160                 cx.error_spanned_by(
161                     variant.original,
162                     "#[serde(variant_identifier)] may only contain unit variants",
163                 );
164             }
165         }
166     }
167 }
168 
169 /// Skip-(de)serializing attributes are not allowed on variants marked
170 /// (de)serialize_with.
check_variant_skip_attrs(cx: &Ctxt, cont: &Container)171 fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) {
172     let variants = match &cont.data {
173         Data::Enum(variants) => variants,
174         Data::Struct(_, _) => {
175             return;
176         }
177     };
178 
179     for variant in variants.iter() {
180         if variant.attrs.serialize_with().is_some() {
181             if variant.attrs.skip_serializing() {
182                 cx.error_spanned_by(
183                     variant.original,
184                     format!(
185                         "variant `{}` cannot have both #[serde(serialize_with)] and #[serde(skip_serializing)]",
186                         variant.ident
187                     ),
188                 );
189             }
190 
191             for field in &variant.fields {
192                 let member = member_message(&field.member);
193 
194                 if field.attrs.skip_serializing() {
195                     cx.error_spanned_by(
196                         variant.original,
197                         format!(
198                             "variant `{}` cannot have both #[serde(serialize_with)] and a field {} marked with #[serde(skip_serializing)]",
199                             variant.ident, member
200                         ),
201                     );
202                 }
203 
204                 if field.attrs.skip_serializing_if().is_some() {
205                     cx.error_spanned_by(
206                         variant.original,
207                         format!(
208                             "variant `{}` cannot have both #[serde(serialize_with)] and a field {} marked with #[serde(skip_serializing_if)]",
209                             variant.ident, member
210                         ),
211                     );
212                 }
213             }
214         }
215 
216         if variant.attrs.deserialize_with().is_some() {
217             if variant.attrs.skip_deserializing() {
218                 cx.error_spanned_by(
219                     variant.original,
220                     format!(
221                         "variant `{}` cannot have both #[serde(deserialize_with)] and #[serde(skip_deserializing)]",
222                         variant.ident
223                     ),
224                 );
225             }
226 
227             for field in &variant.fields {
228                 if field.attrs.skip_deserializing() {
229                     let member = member_message(&field.member);
230 
231                     cx.error_spanned_by(
232                         variant.original,
233                         format!(
234                             "variant `{}` cannot have both #[serde(deserialize_with)] and a field {} marked with #[serde(skip_deserializing)]",
235                             variant.ident, member
236                         ),
237                     );
238                 }
239             }
240         }
241     }
242 }
243 
244 /// The tag of an internally-tagged struct variant must not be
245 /// the same as either one of its fields, as this would result in
246 /// duplicate keys in the serialized output and/or ambiguity in
247 /// the to-be-deserialized input.
check_internal_tag_field_name_conflict(cx: &Ctxt, cont: &Container)248 fn check_internal_tag_field_name_conflict(cx: &Ctxt, cont: &Container) {
249     let variants = match &cont.data {
250         Data::Enum(variants) => variants,
251         Data::Struct(_, _) => return,
252     };
253 
254     let tag = match cont.attrs.tag() {
255         TagType::Internal { tag } => tag.as_str(),
256         TagType::External | TagType::Adjacent { .. } | TagType::None => return,
257     };
258 
259     let diagnose_conflict = || {
260         cx.error_spanned_by(
261             cont.original,
262             format!("variant field name `{}` conflicts with internal tag", tag),
263         );
264     };
265 
266     for variant in variants {
267         match variant.style {
268             Style::Struct => {
269                 for field in &variant.fields {
270                     let check_ser = !field.attrs.skip_serializing();
271                     let check_de = !field.attrs.skip_deserializing();
272                     let name = field.attrs.name();
273                     let ser_name = name.serialize_name();
274 
275                     if check_ser && ser_name == tag {
276                         diagnose_conflict();
277                         return;
278                     }
279 
280                     for de_name in field.attrs.aliases() {
281                         if check_de && de_name == tag {
282                             diagnose_conflict();
283                             return;
284                         }
285                     }
286                 }
287             }
288             Style::Unit | Style::Newtype | Style::Tuple => {}
289         }
290     }
291 }
292 
293 /// In the case of adjacently-tagged enums, the type and the
294 /// contents tag must differ, for the same reason.
check_adjacent_tag_conflict(cx: &Ctxt, cont: &Container)295 fn check_adjacent_tag_conflict(cx: &Ctxt, cont: &Container) {
296     let (type_tag, content_tag) = match cont.attrs.tag() {
297         TagType::Adjacent { tag, content } => (tag, content),
298         TagType::Internal { .. } | TagType::External | TagType::None => return,
299     };
300 
301     if type_tag == content_tag {
302         cx.error_spanned_by(
303             cont.original,
304             format!(
305                 "enum tags `{}` for type and content conflict with each other",
306                 type_tag
307             ),
308         );
309     }
310 }
311 
312 /// Enums and unit structs cannot be transparent.
check_transparent(cx: &Ctxt, cont: &mut Container, derive: Derive)313 fn check_transparent(cx: &Ctxt, cont: &mut Container, derive: Derive) {
314     if !cont.attrs.transparent() {
315         return;
316     }
317 
318     if cont.attrs.type_from().is_some() {
319         cx.error_spanned_by(
320             cont.original,
321             "#[serde(transparent)] is not allowed with #[serde(from = \"...\")]",
322         );
323     }
324 
325     if cont.attrs.type_try_from().is_some() {
326         cx.error_spanned_by(
327             cont.original,
328             "#[serde(transparent)] is not allowed with #[serde(try_from = \"...\")]",
329         );
330     }
331 
332     if cont.attrs.type_into().is_some() {
333         cx.error_spanned_by(
334             cont.original,
335             "#[serde(transparent)] is not allowed with #[serde(into = \"...\")]",
336         );
337     }
338 
339     let fields = match &mut cont.data {
340         Data::Enum(_) => {
341             cx.error_spanned_by(
342                 cont.original,
343                 "#[serde(transparent)] is not allowed on an enum",
344             );
345             return;
346         }
347         Data::Struct(Style::Unit, _) => {
348             cx.error_spanned_by(
349                 cont.original,
350                 "#[serde(transparent)] is not allowed on a unit struct",
351             );
352             return;
353         }
354         Data::Struct(_, fields) => fields,
355     };
356 
357     let mut transparent_field = None;
358 
359     for field in fields {
360         if allow_transparent(field, derive) {
361             if transparent_field.is_some() {
362                 cx.error_spanned_by(
363                     cont.original,
364                     "#[serde(transparent)] requires struct to have at most one transparent field",
365                 );
366                 return;
367             }
368             transparent_field = Some(field);
369         }
370     }
371 
372     match transparent_field {
373         Some(transparent_field) => transparent_field.attrs.mark_transparent(),
374         None => match derive {
375             Derive::Serialize => {
376                 cx.error_spanned_by(
377                     cont.original,
378                     "#[serde(transparent)] requires at least one field that is not skipped",
379                 );
380             }
381             Derive::Deserialize => {
382                 cx.error_spanned_by(
383                     cont.original,
384                     "#[serde(transparent)] requires at least one field that is neither skipped nor has a default",
385                 );
386             }
387         },
388     }
389 }
390 
member_message(member: &Member) -> String391 fn member_message(member: &Member) -> String {
392     match member {
393         Member::Named(ident) => format!("`{}`", ident),
394         Member::Unnamed(i) => format!("#{}", i.index),
395     }
396 }
397 
allow_transparent(field: &Field, derive: Derive) -> bool398 fn allow_transparent(field: &Field, derive: Derive) -> bool {
399     if let Type::Path(ty) = ungroup(field.ty) {
400         if let Some(seg) = ty.path.segments.last() {
401             if seg.ident == "PhantomData" {
402                 return false;
403             }
404         }
405     }
406 
407     match derive {
408         Derive::Serialize => !field.attrs.skip_serializing(),
409         Derive::Deserialize => !field.attrs.skip_deserializing() && field.attrs.default().is_none(),
410     }
411 }
412 
check_from_and_try_from(cx: &Ctxt, cont: &mut Container)413 fn check_from_and_try_from(cx: &Ctxt, cont: &mut Container) {
414     if cont.attrs.type_from().is_some() && cont.attrs.type_try_from().is_some() {
415         cx.error_spanned_by(
416             cont.original,
417             "#[serde(from = \"...\")] and #[serde(try_from = \"...\")] conflict with each other",
418         );
419     }
420 }
421