-
Notifications
You must be signed in to change notification settings - Fork 296
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
Elements ignored when encoding struct embedding a Selfer #141
Comments
To workaround ugorji/go#141
This is a limitation of Go's embedded rules. There's no way from within Go's reflection mechanism that you can determine whether a type implements an interface directly or if it does it based on what it embedded. The only other option would be to statically look at every file in the current directory to see if the type has an exact implementation of the types. I tried going down that road before, but it was fraught with peril. Currently, codecgen depends on a runtime analysis of the reflection information and static AST of the file(s) that you are running codecgen upon. Unfortunately, I don't think this can be fixed. This has been a headache for me also! I have spent days trying to resolve this, and finally punted and put it on users to understand how Go's embedding works. See #100 for another place where this was exhibited. BTW, what was the problem with -tags=unsafe? This should be a low-cost, high-impact change for you. |
Closing as "WorkAsDesigned". See #100 for similar manifestation of the Go Reflection Limitation and how Go works per inheriting implementations of embedded types. |
EDIT: You are right. I just confirmed that this is the behavior for What was happening in our case was that we were only using codecgen for a package ( type ControlInstance struct {
ProbeID string `json:"probeId"`
NodeID string `json:"nodeId"`
report.Control
} The same would had happened if we defined custom However, one rarely implements Thanks for the explanation. |
I didn't investigate in depth, but I am getting binary garbage on the decoder's end (both with JSON and Msgpack). I am using Would |
Check out https://play.golang.org/p/LiVUN4-Z0F to see that this is a limitation of go's rules that even encoding/json cannot work around. |
In addition, this issue happens if you implement encoding/json.(M|Unm)arshaler or the replacements encoding.Text(M|Unm)arshaler. So the impact is spread. |
Regarding the unsafe, it is sufficient to pass the -u flag to codecgen. The unsafe use is pretty low-risk also. It is only used when encoding structs, as we don't convert []byte to string when decoding the name of the struct field just so we can do a match. Please explain what you mean by "I am getting binary garbage on the decoder's end". |
When I compile the library with I can prepare a branch and some instructions to reproduce if that's OK with you, I don't have time to work on an isolated right now. |
I have bad news. 5d64d76 doesn't work for structs with embedded types, because its's using reflection. In the example I used above: type ControlInstance struct {
ProbeID string `json:"probeId"`
NodeID string `json:"nodeId"`
report.Control
} The An AST-based approach would work (I was assuming you were going to do this, sorry for not warning you in advance). However, it would require the |
9f21f8e does fix the problem I was experiencing, thanks! However compiling with |
@2opremio I was aware that we could use the AST, but it was fraught with peril (even when I tried to do it previously). For example:
I really wanted this, and really wanted reflection to support it, but can't use it because we support down to Go 1.3. |
@2opremio per the -tags=unsafe, yes, that mostly is used in non-codecgen (aka reflection) mode. In codecgen mode, most of the reflection code is bypassed, and so the unsafe generated one is used. However, where you have interface fields within your structs, you would still benefit from the -tags=unsafe. This is because codecgen cannot do anything with interfaces, so those have to use the reflection-based code. |
It 'kinda' works only because of an ordering tweak in the code generation. It will break as soon as codecgen.detailed.go is regerated without removing report.codecgen.go first, which is not acceptable. See ugorji/go#141 (comment) * Bump github.com/ugorji/go/codec
If you are not bought on the AST solution (and it seems you are not) I think it's safer to simply revert 5d64d76 . It makes code-generation non-deterministic because of ordering and it will break the codecs of users (at the very least it breaks mine :) ).
Well, they are used very differently. Users will rarely create their own Selfers explicitly, and if they do, only a small amount of types will be covered. However, when using codecgen users will generate Selfers for almost all types. This makes a reflection-based detection of Selfers much worse in codecgen.
5d64d76 breaks this. If we look at
In fact, codecgen without 5d64d76 is also broken in this respect. Let's now assume that the user himself defines a selfer for
If I were to choose, I would go for the mixed AST+Selfer detection approach, followed reverting 5d64d76 but providing a mechanism to disable automatic Selfer-generation for certain types (like #140) |
I think we now use explicit |
Having a hard time following the logic. To be clear, the only design principle is: If this design principle is not honored, it's a bug and I will be happy to fix it. It's possible that one can alter the default behaviour by careful ordering. Once manual invocation is done, that's a possibility. If we can prevent this, we should also. Since go doesn't allow circular package dependenciess, then there is always a clear non-circular dependency graph. You should mirror that in running codecgen. Embedding is fraught with peril because serialization is by default reflection-based, and Go always containing structs inherit the methods of anonymous fields. The best solution here is: So I agree: 5d64d76 was a real bug that we fixed. With the fix, both reflection and codecgen work the same way at runtime. |
My point is 5d64d76 causes codecgen to break the serialization of embedded fields, even if you don't explicitly define how they are serialized. EDIT: Crap, I meant 5d64d76 instead of 9f21f8e in all the comments above. I changed it, sorry. |
I think you mean 5d64d76 . I'm still not clear what "break" means. If it was already "broken" in reflection mode, then it's already broken, and we just maintain the same behaviour in code-generation mode. Question: can you explain what you mean by "it breaks serialization"? I just want to ensure that I understand exactly what you are alluding to. Note that This was a sore spot for me too. I had already written the code to handle it (ie look through all files in the folder, find the CodecSelfer methods, track them, etc), before I realized that tests broke because behaviour between reflection and codecgen mode was different. I had to remove the change, drugingly. That's why I said it was fraught with peril. I know I sound like I'm defending the behaviour. But I actually am sad; I gave up fighting it and just resigned to how it works. Embedding is good, until it's not, and it's a tough lesson. When you embed, you get to inherit ALL the methods, even those you don't want. In my code now, I only embed types which are purely value types, or if the method is consistent e.g. a type has an id field and adds an ID method, which all containing types must have. |
Yes, I am deeply sorry, I am used to keep track of issue numbers not git SHAs. I have corrected it. Do my comments make more sense now?
Let's get back to my example: type ControlInstance struct {
ProbeID string `json:"probeId"`
NodeID string `json:"nodeId"`
report.Control
} I don't explicitly define selfers for any of the fields so, the non-codecgen behavior is to serialize all elements of With 5d64d76, if codecgen generates This contrasts with codecgen before 5d64d76 and the non-codecgen behavior, and that's what I mean by it breaks serialization for embedded types. But not only that, 5d64d76 makes code generation non-deterministic, which is a maintenance nightmare. Even if I come up with an initial code-generation order which makes it work (i.e. serialize the |
I understand. Do you see how 5d64d76 doesn't respect that principle for embedded types due to its impact of code-generation ordering? I would much rather get compile-time clashes on Selfers (i.e. pre-5d64d76) than make the encoding/decoding behavior depend on the code-generation order. |
A solution would be to modify 5d64d76 so that it skipped generating |
With your example, you should want to run codecgen in dependency order
If you don't run the codecgen in dependency order, you have bad results e.g. Selfer method on a regular field is not called because the Selfer method on that type was not yet done, etc. Best practice is:
With this, you get same behaviour as reflection mode. Assuming you write custom Selfer implementations for some types, then the system will behave completely the same whether or not you do 1. and/or 2. above. There's really no way around it: if you embed a type which defines a known method, that method will be used at runtime, and codecgen shouldn't help you workaround that. I don't think there's any way to workaround this. Gotta just bite the bullet, and be deliberate when you embed types which have methods which could affect behaviour. It's a tough pill to swallow ;( |
I guess you mean the other way around, otherwise the non-embedded fields will be ignored.
True, but:
I am not an expert in Go's Codecgen could generate an extra method identifying the type for which the Selfer was generated. For instance, when invoking codecgen for type func (*Foo) GetCodecgenTypeName() string{
return "Foo"
}
func (*Foo) CodecEncodeSelf(*Encoder) {
}
func (*Foo) CodecDecodeSelf(*Decoder) {
} Then, you could modify 5d64d76 to only skip generating a
(2) ensures that the |
You are suggesting ways to workaround it, but i'm not willing to look at workarounds because the fundamental premise is broken i.e. enclosing types inherit the embedded methods, and there's no workaround for that. Anything we do will cause discordance. Forget the inconvenience of the ordering, etc. The main design principle doesn't give us room to play. |
Alright, I give up :) Are you still willing to merge #140 ? |
Yes - definitely will merge #140. I can't look at it today (working on a On Wed, Feb 17, 2016 at 11:36 PM, Alfonso Acosta [email protected]
|
* Bump github.com/ugorji/go/codec, to get fixes which make github.com/2opremio/go-1/codec unnecessary * Remove github.com/2opremio/go-1/codec * Remove type embeddings to avoid the problem explained at ugorji/go#141 (comment)
* Bump github.com/ugorji/go/codec, to get fixes which make github.com/2opremio/go-1/codec unnecessary * Remove github.com/2opremio/go-1/codec * Remove type embeddings to avoid the problem explained at ugorji/go#141 (comment)
* Bump github.com/ugorji/go/codec, to get fixes which make github.com/2opremio/go-1/codec unnecessary * Remove github.com/2opremio/go-1/codec * Remove type embeddings to avoid the problem explained at ugorji/go#141 (comment)
To workaround ugorji/go#141
* Bump github.com/ugorji/go/codec, to get fixes which make github.com/2opremio/go-1/codec unnecessary * Remove github.com/2opremio/go-1/codec * Remove type embeddings to avoid the problem explained at ugorji/go#141 (comment)
Here's a simplified repro:
The program above outputs:
completely ignoring the
Bar
element.I would expect something in the lines of:
Or, with a properly defined selfer:
I guess that, since the embedded Selfer makes the full struct a Selfer too, the encoder is blindly applying
(e Embedded) CodecEncodeSelf()
, ignoring other fields. I think the Encoder must somehow distinguish between the case in which the struct embeds a Selfer and when the struct itself implements a Selfer directly.I haven't tried it yet it but, as a workaround, I guess one can define a Selfer for Foo.
I haven't tested the decoding, but maybe that's broken too.
The text was updated successfully, but these errors were encountered: