-
-
Notifications
You must be signed in to change notification settings - Fork 788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"cannot infer type" from Deserialize derive macro with simple variants and untagged variants #2613
Conversation
variants and untagged variants
quote_block! { | ||
let __content = <_serde::__private::de::Content as _serde::Deserialize>::deserialize(__deserializer)?; | ||
let __deserializer = _serde::__private::de::ContentRefDeserializer::<__D::Error>::new(&__content); | ||
|
||
#first_attempt | ||
#( | ||
if let _serde::__private::Ok(__ok) = #attempts { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the type annotation is added here, we can keep sharing the logic between first_attempt
and attempts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed! As I noted, I was trying to minimize code change for code paths unrelated to the new untagged variant semantics, but I'm very happy to add the type annotation in all cases if that's preferable. Please let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
From skimming the generated code for the enum in your PR description, after this fix, it ends up looking like:
if let _serde::__private::Result::<_, __D::Error>::Ok(__ok) = {
let (__tag, __content) = _serde::Deserializer::deserialize_any(...)?;
let __deserializer = _serde::__private::de::ContentDeserializer::<__D::Error>::new(__content);
match __tag {
__Field::__field0 => {
_serde::Deserializer::deserialize_any(__deserializer, ...)?;
_serde::__private::Ok(Data::A)
}
}
} {
return _serde::__private::Ok(__ok);
}
if let _serde::__private::Ok(__ok) = _serde::__private::Result::map(
<u32 as _serde::Deserialize>::deserialize(__deserializer),
Data::Var,
) {
return _serde::__private::Ok(__ok);
}
_serde::__private::Err(_serde::de::Error::custom(...))
That's probably a bad sign if the first if let
can only ever be Ok or early return. I don't think just adding a type fixes that bug.
For an externally tagged enum (which is the only case that #2403 tested) untagged variants work like this:
#[derive(Deserialize, Debug)]
pub enum Data {
A,
#[serde(untagged)]
Var(u32),
}
fn main() {
let a = r#" "A" "#;
println!("{:?}", serde_json::from_str::<Data>(a)); // Ok(A)
let var = r#" 1 "#;
println!("{:?}", serde_json::from_str::<Data>(var)); // Ok(Var(1))
}
For an internally tagged enum, it fails to compile before this PR, and I think it has incorrect behavior after this PR.
#[derive(Deserialize, Debug)]
#[serde(tag = "t")]
pub enum Data {
A,
#[serde(untagged)]
Var(u32),
}
fn main() {
let a = r#" {"t":"A"} "#;
println!("{:?}", serde_json::from_str::<Data>(a)); // Ok(A)
let var = r#" 1 "#;
println!("{:?}", serde_json::from_str::<Data>(var)); // Err
}
Could you or @dewert99 look into making this deserialize Ok(Var(1))
?
Otherwise we should add an intentional error message that this case is not supported (untagged variant in internally tagged enum).
Thanks for taking a look @dtolnay
That doesn't seem intrinsically problematic to me (i.e. someone new to this code). It seems like #2403 intended to reuse as much existing logic as possible--which seems like a reasonable goal.
Oof. Nice catch. This is more problematic than I had realized. I think I've found a relatively simple fix, but there are some weird semantics here. First, here's the gist of the fix I've been testing out (wrapping // Ignore any error associated with non-untagged deserialization so that we
// can fall through to the untagged variants. This may be infallible so we
// need to provide the error type.
let first_attempt = first_attempt.map(|expr| {
quote! {
if let _serde::__private::Result::<_, __D::Error>::Ok(__ok) = (|| {
#expr
})() {
return _serde::__private::Ok(__ok);
}
}
}); That is: ignore any early returns (in addition to terminal returns) from There is (at least) one more problem: what to do if all variants fail to deserialize? With the change I proposed we get:
That's clearly not right since the enum isn't untagged... at least not in whole. Note that's also the error for an externally tagged enum with untagged variants, which seems like a different, but related, bug. Without the untagged variants we get:
That clearly made sense before #2403 but I'm not sure it's sufficient now; also consider an (annoying) enum like this: #[derive(Serialize)]
#[serde(tag = "potato")]
enum X {
#[serde(untagged)]
A { x: u32 },
#[serde(untagged)]
B { y: y32},
} It's internally tagged... sort of? Or is it untagged since all the variants are untagged? How should we talk about it in error messages? Perhaps the error message for an internal or adjacently tagged enum that contains untagged variants should instead be a little more explicit:
For an externally tagged enum perhaps: if the data is either a string or an object with a single:
otherwise:
I'm happy to pursue this, but would appreciate guidance regarding the proposed fix, error messages, and any tests you'd suggest I crib to improve the robustness of testing here. I think the genie is already out of the bottle, but I do want to ask if untagged variants is a feature you want to commit to. I personally love it, but I can see that it may have added unanticipated complexity (along the lines of #1646 (comment)). |
@dtolnay could you take another look? I think I've got the other enum tagging types covered; I would appreciate input about error messages in particular. |
Howdy! Any advice for moving this PR forward? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
I think the "data did not match any variant of untagged enum Data" error message is not a big deal — I wouldn't block releasing this fix on improving the message. But your suggested messages in #2613 (comment) make sense to me. I would be glad if someone wants to pursue those improvements.
Regarding robust testing, @Mingun can perhaps give guidance since they have been reorganizing and fleshing out the tests recently.
This produces an error:
My proposed fix is intended to minimize changes to code unrelated to the changes from #2403.