rust-lang / rust-lang/rust-clippy
Feature request: Comprehensive check for conflicting map keys in Serde
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
This feature request is based on this issue: https://github.com/serde-rs/serde/issues/1181
Long story short: since proc-macro-derive runs on one type at a time, and it's a purely syntactic process without type checking information, serde_derive itself can't check for certain sneaky serialization errors that result in duplicate map keys. Example:
- Suppose an internally-tagged enum has a newtype variant which itself contains an internally-tagged enum, either…
- directly, or
- indirectly, by means of being nested in 1 or more levels of untagged enums with a newtype variant.
- Now:
- if the containing and contained internally-tagged enums have the same type tag, or
- if a struct variant of the contained enum has a field with the same name as the type tag,
then they end up being serialized at the same level, generating a conflict.
See the following MVCE:
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate serde;
// internally-tagged top-level enum
#[derive(Serialize)]
#[serde(tag = "same")]
enum Foo {
Qux(Z), // newtype variant around another internally-tagged enum itself
Bar(X), // newtype variant around two levels of untagged, then one internally-tagged enum
New(E), // newtype variant containing an untagged enum containing a struct variant with a conflicting field name
}
#[derive(Serialize)]
#[serde(untagged)]
enum X {
Y(Y)
}
#[derive(Serialize)]
#[serde(untagged)]
enum Y {
Z(Z),
}
#[derive(Serialize)]
#[serde(tag = "same")]
enum Z {
Z,
}
#[derive(Serialize)]
#[serde(untagged)]
enum E {
F(F),
}
#[derive(Serialize)]
#[serde(tag = "different")]
enum F {
Struct { same: () },
}
fn main() {
println!("{}", serde_json::to_string_pretty(&Foo::Bar(X::Y(Y::Z(Z::Z)))).unwrap());
println!("");
println!("{}", serde_json::to_string_pretty(&Foo::Qux(Z::Z)).unwrap());
println!("");
println!("{}", serde_json::to_string_pretty(&Foo::New(E::F(F::Struct { same: () }))).unwrap());
}
Output:
{
"same": "Bar",
"same": "Z"
}
{
"same": "Qux",
"same": "Z"
}
{
"same": "New",
"different": "Struct",
"same": null
}
Checking for these conflicts requires traversing the transitive closure of the contained variants and types, which I think necessitates proper type checking. I will try and dive deep into Clippy and start implementing a lint pass that realizes this check. All feedback and suggestions are welcome.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the linked Serde issue and the MVCE in this issue, then investigate Clippy's lint-pass infrastructure and how it obtains type information. Done means detecting the described transitive duplicate-map-key cases and reporting them as a Clippy lint, with coverage for the direct, nested untagged, and conflicting-field examples.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- devtools
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100