-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
proposal: reflect: allow creation of recursive types #39528
Comments
Good to hear that I am not the only one interested in adding this feature! :) Some days ago I had almost exactly the same idea, with some minor differences: // create a new, incomplete, named Type with Kind = Invalid.
// the parameter 'pkgpath string' is stored in the type, but does not grant any special permission:
// for example, creating a type in package "runtime" does not give it access to the package's
// unexported symbols.
// TBD: decide: accept or reject pkgpath+name equal to a pre-existing type?
func NewNamed(pkgpath string, name string) Type
// must be called exactly once before Complete()
func SetUnderlying(named Type, underlying Type)
// cannot be implemented yet because of API/ABI limitations... I would keep this for later
// func AddMethod(named Type, method Method)
// must be called exactly once to "freeze" the type and make it immutable,
// internally it will mark the type as complete and make it fully usable as any other reflect.Type
func Complete(named Type) I think it's better than having a single "do-everything" function func Bind(named Type, underlying Type, methods []Method) for three reasons:
If this proposal (or the above one, or something similar enough) is accepted, I can help implementing it |
@cosmos72 The creation process is already quite involved, what with the invening calls to the PtrTo and SomethingOf functions, so I don't see a problem splitting my The important thing, I think, is that there is a point when the new type is final (my Bind or your Complete); I've added an open question regarding goroutine-safety to my original proposal. Maybe the next logical step is to see whether our proposals can be implemented without too much hassle. |
I had a cursory glance at |
Yes, There is also a property of https://golang.org/pkg/reflect/#Type states: I hope I'm wrong, but trying to imagine how
causes many conflicts with such guarantee |
True. One possible solution is that this guarantee simply does not hold until This also means that I guess a similar situation arises with goroutine-safety. Probably the involved types do not become goroutine-safe until |
What about Go 1.x compatibility promise ?
which you suggest to break. That's why I preferred your original proposal: I am worried that your idea "Bind has to walk recursively through the underlying type and replace all references to the placeholder with the real thing, possibly updating the type cache on the way"
|
Is there any way to gain access to a This way, older source will continue to compile and run as laid out in the compatibility guidelines.
I've had another look at the code and apparently these caches are for the benefit of the If my assumption above about being able to keep the return value of |
I'm not sure what you mean by keeping a value close. Certainly once a program has a |
The idea is that there is a special contract for callers of Now, this contract is not enforceable at the language level. If someone wants to shoot themselves in the foot, they certainly can pass a Is such a contract acceptable for the Go community? From your and @cosmos72's comments I get the notion the answer is "no". Is that correct? If so, we need an API which avoids these "special" reflect.Type instances altogether. I'll write another proposal later. |
Also, Also, the All in all, it seems an API easy to use incorrectly, and such misuses are difficult to detect. That's why I prefer the initial proposal, where |
Until now, a valid identifier check was only required for isValidFieldName. For golang#39528, a valid identifier check will also be required for type names. Updates golang#39528.
Introduce two new functions, Named and Bind, to the reflect API to create possibly recursive, named types. Fixes golang#39528.
OK, but even with an in-place modification, the FWIW, I implemented the proposal in this issue here: https://github.com/TheCount/go/tree/issue-39528. If you think it's worthwhile pursuing, maybe you can try to change my implementation to an in-place modification (I don't know how to do that, I guess you'd have to somehow interact with the memory management system). |
True, an incomplete type can only be used in few cases. Essentially only to build other types - calling other function/methods with it will (and should) panic.
I understand the reason, but it had the side effect of creating a
I have a draft implementation using in-place modification (UPDATE: published at https://github.com/cosmos72/go/commit/b010cd5c67dac0856fea156d7e6d6c3b0e3f1d77) var t = reflect.Named("foo")
var arrayOfT = reflect.ArrayOf(42, t)
reflect.Bind(t, reflect.TypeOf(int(0))) both I'd say it's time to wait for feedback from Go team on whether either proposal is acceptable. |
Small misunderstanding here. I meant I wanted to write (yet) another proposal which avoids even the problem of the limited incompleteness between |
In your draft, you define your wrapper type as type wrapperType struct {
rtype
uncommon uncommonType
under *rtype
} How do you go from this to the memory layout of the finished type as the runtime expects it? For example, a function type in memory would look like (pseudocode): struct {
rtype
numIn, numOut uint16
uncommonType
[numIn+numOut]*rtype
} Since you can't know |
That's not necessary. A patched implementation of func (t *rtype) Field(i int) StructField {
if t.Kind() != Struct {
panic("reflect: Field of non-struct type " + t.String())
}
if t.tflag&tflagWrapper != 0 {
t = (*wrapperType)(unsafe.Pointer(t)).underlying("Field")
}
tt := (*structType)(unsafe.Pointer(t))
return tt.Field(i)
} The idea is to forward (almost) any call to the underlying type, which has the correct layout. [UPDATE] slightly more complete implementation published at https://github.com/cosmos72/go/tree/issue-39528 |
OK, I think I understand now. You introduce a completely new "proxy" layout. That means of course checking and updating all parts of the Go source code where a pattern like var something interface{}
…
explicit := *(**expectedLayout)(unsafe.Pointer(&something)) is used. Possibly also third-party tools. But nothing covered by the Go compatibility promise. [edit: fixed pattern] |
Partially constructed types are definitely a major disadvantage. Then everything that accepts a reflect.Type has to guard against them. Given that you've opened the other issue, let's close this one as a duplicate of #39717. |
Introduction
In #20013, @ben-clayton asked for runtime creation of recursive structs. The analogous issue arises with several other constructions which are legal at compile time, e. g.:
While these specific definitions are not as useful at the top-level as a struct, combinations can be, e. g.
or
The issue is related to #16522, where @crawshaw proposes an API to create a named type with a method set. In that issue, @cosmos72 observed that the proposed API would not allow for the creation of recursive types, and also referenced https://golang.org/pkg/go/types/#Named, where recursive named types can be represented in a two-step process. In #16522, it was also observed that the implementation of a runtime method set is met with certain challenges, so this proposal will concentrate on recursive types while leaving the door open for the core concern of #16522.
Proposal
This issue proposes an API for a two-step process of runtime recursive type creation similar to, but not quite analogous to https://golang.org/pkg/go/types/#Named. The first step is to create an incomplete named type which can only be used in certain restricted contexts until it is completed in the second step by binding the name to an underlying type specification, in analogy to a compile-time type declaration.
(edit: changed return value of
Bind
)Here, the methods would only be added at the Bind stage (in a future version), so that they can refer to
named
.Open questions
Named
? The spec suggestsletter { letter | unicode_digit }
, but that may be expensive to check. Also, should the name be the name of an exported type?PkgPath()
method of the created type return? Probably an empty string, but could also be a pseudo package name where all the types created at runtime end up in.Kind()
method of the named type return before Bind? Of the existing kinds, onlyInvalid
makes sense. Could introduce a new kind, or use the tflags to mark the type incomplete.Bind
has been called on it.The text was updated successfully, but these errors were encountered: