1use internals::ast::{Container, Data, Field, Style};
2use internals::attr::{Identifier, TagType};
3use internals::{Ctxt, Derive};
4use syn::{Member, Type};
5
6pub 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
19fn 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
42fn 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
60fn 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
81fn 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 (_, 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 (_, 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 (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 (_, 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 (_, Identifier::No, false, _) => {}
138
139 (Style::Unit, _, false, _) => {}
141
142 (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
169fn 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
244fn 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
293fn 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
312fn 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
391fn member_message(member: &Member) -> String {
392 match member {
393 Member::Named(ident) => format!("`{}`", ident),
394 Member::Unnamed(i) => format!("#{}", i.index),
395 }
396}
397
398fn allow_transparent(field: &Field, derive: Derive) -> bool {
399 if let Type::Path(ty) = 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
413fn 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}