-
-
Notifications
You must be signed in to change notification settings - Fork 314
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
Type unification always fails with embedded structs in a resource due to field naming contradiction #624
Comments
We talked a bit about this on IRC... I think this is blocked on golang/go#25401 ... So:
I should probably remove this comment, but maybe it's fine to leave in to at least provoke thought in future passers by? |
I'm happy to work on this. It's something I want to rely on in a resource that I'm working on, and I think fixing/changing this behavior will appear more intuitive and consistent in the long run.
I will submit a PR for a failing test case, then attempt to actually make the field mapping work, which is proving rather difficult, unsurprisingly. The TODO causes no harm as it is, but also it would be good to clean through the old comments and prune anything inaccurate or resolved as an act of declaring stability. |
Struct field matching between lowercase/uppercase names even with `lang:"name"` tags highlighting the issue in purpleidea#624, with the hope that it is fixed so the test case can be updated. Signed-off-by: Joe Groocock <me@frebib.net>
After thoroughly straining every neuron in my brain trying to trace through the several layers of the code, I think I understand the variable journey from mcl to resource now. Fixing the primary part of this is simple, by tracking the name of the Golang fields, the variables can be mapped when matching the structs up. The code change for that part is pretty simple (https://gist.github.com/frebib/f5409ff4719a465489c6ec464eefc66c/raw, with some cleanup; I'm still hacking at this) Now the part I'm stuck at wasn't apparent to me at first, but makes sense: above takes the data from the mcl struct into the intermediate AST/graph form. We haven't covered converting it back into the resource yet. Line 729 in 01c2131
but are set at the top-level in the struct, i.e. there is no recursion through the structs, maps etc to resolve anything. An example to explain ^ better: type Res {
X struct{ SomeName string }
}
value := struct{ somename string }{
somename: "this field will not appear",
}
...
// will succeed but SomeName will be an empty/default value
x.FieldByName("X").Set(value) The problem is that we have the data in the The fix for this is to map the field names in Lines 6537 to 6558 in 01c2131
I can come up with two solutions, neither of which are straight forward:
I'm not which option I prefer. Numero 2 would be cleaner, but is also a larger change, and possibly slower to execute too |
There's a lot to discuss here, and TBH, I think we might be near the limit of how efficient we can be over text... (Of course please do attempt to send appropriate patches and tests that I can merge if you like...) However, if it would help, we can do a gmeet session on the weekend and try and live hack this out if you'd like. It would likely take at least an hour I think because I've not looked at this code recently, but if that's helpful to you, lmk...
I don't disagree... Kindly remember that when maintaining a big complex project with no budget you heavily prioritize what you work on and aren't always happy with everything.
I think I'm roughly okay with this sort of thing modulo some harsh style nitpicking ;) -- This lib is used everywhere so I need to be careful too.
This relies on the core golang reflect magic. There may be situations where a more complex, verbose implementation could replace something I've taken a short-cut on. I can't guarantee this, but I think it's likely. Set may be part of it... Also some of my methods in the types package are implemented as "hacks"... You should know what I mean if you have a deeper look. HTH |
Seems this is fixed with your recent patches! Thanks =D |
Versions:
mgmt --version
): 01c2131 (master HEAD)uname -a
): Archgo version
): 1.15.6Description:
This bug (oversight? I'm not sure) was actually already discovered 2 years ago #432 (comment) in a similar issue that may actually be the same root problem as this. The error thrown is
Using the example from that comment we can see that the resource struct has a struct child, which in theory mcl should support just fine:
with some matching mcl ala
logic would dictate that this should work fine. The structs are tagged with the
lang
tag to indicate that the mcl fields should be matched to the struct field with the same tag: mclrange.from
should match to the Go fieldRange.From
. This is exactly how it works for resource struct fields. Even thelang
struct tags are superfluous because the "default" behaviour is to use the struct field name in lowercase. That is the expected behavior and if it's not already explicitly documented, it probably should be.The lang tags are the first clue as to what the issue is here. Looking at the code, we'd expect to find something doing roughly:
What the code actually does, is convert the Go
reflect.Type
for the struct into a mgmttype.Type
withTypeOf
mgmt/lang/types/type.go
Lines 137 to 157 in 01c2131
type.Type
struct against the struct parsed from the mcl. Note there is no "conversion" in there of Go struct field names to mcl field names: they are simply copied verbatim. This entirely ignores thelang
tag on fields if it's provided, and the implicit "lowercase by default" behaviour is skipped here too.Herein lies the first issue. Looking forward a little, the "converted" Go struct into this lang representation with uppercase named fields is compared directly with the mcl parsed struct during the unification phase, which is a logical contradiction. The two structs with fields of differing names will never match. The naive answer to this would be to just make the Go struct use lowercase names to match the mcl struct, so then they will match, but of course this isn't the correct answer either because lowercase field names in Go are unexported, therefore cannot be reflected. With these constraints understood it's clear that the current "take structs at face value" approach can never work.
Now, of course, we could just fix the code to convert Go structs to take the name conversion into account. A tiny change such as this is enough to convert uppercase Go struct field names to lowercase according to the
lang
tag, which actually makes the above example work:but this isn't all ☀️ and 🌈 either. It does make the type unification succeed because the struct field names match now, but a little while after starting mgmt, it panics because
type.Reflect
is called on a struct with unexported fields here:mgmt/lang/types/type.go
Lines 742 to 767 in 01c2131
#432 was raised for exactly this problem, and I think only got half way to discovering what the underlying issues are.
I do find this situation a bit odd though, because this behaviour isn't present for the struct that represents the resource. Both name conversions (implicit and explicit) work fine between the Go struct and the mcl resource definition. This indicates that the codepaths for this behaviour are different and it doesn't use the general case struct code, which seems to be the case
mgmt/lang/structs.go
Line 612 in 01c2131
Should the special-casing be removed and this code be unified with the struct handling code? idk
So some takeaways from this that I have gleaned
lang
tags on struct fields are ignored sometimes but they probably shouldn't betypes.Type
data so it's available for consistent bi-directional conversionsMy gut reaction for how to tackle this is probably to track the additional Go field names in the
type.Type
data structure. The field name in that type should always be the lowercase mcl variant, but then additionally (inOrd
or in another field) the Go field name should also be stored, because it will probably never match. I do have concerns about inflating the memory usage of that struct though, so this would have to be a carefully considered change.A side-note to this is I've noticed this comment, which seems to be a good idea, and perhaps this is the appropriate time to rewrite this system to use an interface and sub-types instead of the monolithic Type struct. (A union here would be perfect)
mgmt/lang/types/type.go
Line 58 in 01c2131
The text was updated successfully, but these errors were encountered: