-
-
Notifications
You must be signed in to change notification settings - Fork 706
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
Added Runtime Field Getting and Setting #4070
Conversation
|
That's pretty sweet! Nice! |
|
But, I think it would be better to design a general-purpose solution to the problem of runtime dispatch, instead of a narrow solution that can only set fields. (I will probably follow this up with a proof-of-concept in a little while.) |
|
Updated code to |
You should update the docs, as well. |
|
Below is a generalization of your work. It supports properties as well as fields, and allows getting, setting, or accessing by reference (where possible). The main thing it is missing is support for (non-property) member functions. private template rtPropDispatch(bool set, Prop, bool refProp, Obj, bool refObj)
{
private enum propMix = (set? `` : `return `) ~ `mixin ("obj." ~ mName)` ~ (set? ` = value; return;` : `;`);
private enum dispMix = (set? `void` : (refProp? `ref Prop` : `Prop`)) ~ ` rtPropDispatch(` ~
(refObj? (!set && refProp? `return ` : ``) ~ `ref ` : ``) ~ `Obj obj, string name` ~
(set? `, ` ~ (refProp? `ref ` : ``) ~ `Prop value` : ``) ~ `)
{
` ~ (set? `void` : (refProp? `ref ` : ``) ~ `Prop`) ~ ` checkType(string mName)()
{
if (!__ctfe) assert (0);
` ~ propMix ~ `
}
foreach(mName; __traits (allMembers, Obj))
{
static if(__traits(compiles, checkType!mName()))
{
if(mName == name)
{
` ~ propMix ~ `
}
}
}
assert (0, Obj.stringof ~ " has no " ~ (set? "assignable" : (refProp? "lvalue " : "")) ~ "property \"" ~ name ~ "\" matching " ~ Prop.stringof);
} `;
mixin (dispMix);
}
pragma(inline, true)
{
ref Prop refProp(Prop, Obj)(Obj obj, string name)
if ( is (Obj == class))
{
return rtPropDispatch!(false, Prop, true, Obj, false)(obj, name);
}
ref Prop refProp(Prop, Obj)(return ref Obj obj, string name)
if (!is (Obj == class))
{
return rtPropDispatch!(false, Prop, true, Obj, true)(obj, name);
}
auto getProp(Prop, Obj)(auto ref Obj obj, string name)
{
enum refObj = !is (Obj == class) && (Obj.sizeof > 16);
return rtPropDispatch!(false, Prop, false, Obj, refObj)(obj, name);
}
void getProp(Prop, Obj)(auto ref Obj obj, string name, out Prop value)
{
enum refProp = !is (Prop == class) || (Prop.sizeof > 16);
enum refObj = !is (Obj == class) || (Obj.sizeof > 16);
value = rtPropDispatch!(false, Prop, refProp, Obj, refObj)(obj, name);
}
void setProp(Prop, Obj)(auto ref Obj obj, string name, auto ref Prop value)
{
enum refProp = !is (Prop == class) || (Prop.sizeof > 16);
enum refObj = !is (Obj == class) || (Obj.sizeof > 16);
rtPropDispatch!(true, Prop, refProp, Obj, refObj)(obj, name, value);
}
}(I haven't tested this much; it's just a proof-of-concept.) |
|
I see a small flaw. The original function (not the tsblockman's one) is actually always a template and is not reusable, let's say as a global setter for an aggregate type in particular (because of For example this test does not compile. @safe unittest
{
class Foo {int bar, baz;}
auto foo = new Foo();
auto intSetter = &setField!(Foo,int);
intSetter(foo, "baz", 0);
intSetter(foo, "bar", 1);
assert(foo.baz == 0);
assert(foo.bar == 1);
}Maybe it's possible with two overloads ? |
|
About the naming: The documentation is still inconsistent, it still speaks about attributes. I think "member" is a more common name. Even if you choose not to change the name, it's a good idea to at least mention the word "member" in the documentation to make it easier to find. |
That is a more general term, which also encompasses functions / methods, properties, aliases, nested types, etc. Currently, this PR only works with actual member variables, which are properly called "fields". However, I would like to see it expanded to at least cover getting and setting properties as well, as right now it seems unnecessarily narrowly focused. |
|
Ok, so I updated this using the code from @tsbockman (thanks!), and I made the following changes:
|
978aa48 to
e690a96
Compare
EDIT: The
DMD seems to have suffered a major regression in inlining capability recently. It's not just my code above (which did inline OK when I wrote it) - |
|
Why is the function call impure on 32bit systems? |
|
@JackStouffer I have submitted some corrections to your branch, mostly cosmetic in nature: JackStouffer#1
Attribute inference is failing for If it is manually marked |
|
@tsbockman Thanks for your changes! |
OK I figured it out now, and it's not a compiler bug, after all. Starting from the failing test: class A
{
int bar = 42;
}
auto a = new A();
int val = a.getField!int("bar"); // why is getField() impure?By manual inlining and template instantiation, I determined that the int getField()(A obj, string name)
{
if ("bar" == name)
{
return obj.bar;
}
static if (is(size_t : int)) // true on 32-bit, but not on 64-bit
{
if ("toHash" == name)
{
/* toHash() is a virtual function inherited from Object,
which does not guarantee it to be pure. */
return obj.toHash;
}
}
assert (0, "A has no property matching int");
}This is an expression of a more general problem with runtime dispatch: only the intersection of the restrictive attributes ( Some possible solutions:
I believe that (3) is the best option. Because restrictive attributes are transitive, (1) would severely limit the usefulness of this PR. (2) would simplify use and probably be "good enough" for many cases, but in the general case it has all the same problems as (1). |
|
Also, I finally remembered why I included the |
|
@JackStouffer I have submitted another minor PR: JackStouffer#2 (This does not fix the 32-bit unit tests. I want to think about the best way to do that some more.) |
|
I started a forum thread to see if anyone has a good idea about how to implement solution (3) to the attribute problem: http://forum.dlang.org/post/rqydvgscbrjkrrvkbsnj@forum.dlang.org |
|
I don't know of a solution that doesn't uglify the code other than just to let attribute inference do it's thing. Conceptually, 90% of the time property functions are going to be |
No, because I think a better compromise is to only support |
|
Assuming this passes the auto tester, I am satisfied with this now from a functional perspective. My remaining concerns are minor, and are not blockers: 1) The current naming scheme is a bit awkward - the functions are called a) Rename I would prefer (b) because I tend to think of "property" as a superset of "field" - but either would be OK. Let's just make it consistent, one way or the other. 2) The documentation is kind of repetitive. In particular, should we combine the docs for the two |
|
This looks good on the auto tester now - both 64- and 32-bit are passing. |
|
@Hackerpilot @schuetzm @bbasile Pinging people who commented on this, this is ready to review. |
|
@JackStouffer Any comment on my bike shedding above? |
|
@tsbockman I was an idiot and forgot to pull before I forced pushed :/ Can you send a PR with your brach changes again? |
|
@JackStouffer Please rebase your (For some reason I can't get the unit tests to pass, otherwise.) |
|
@tsbockman done |
|
My previous comment was related to the fact that the function generates, in hard, the code to get each member with a common type (because the member name is a run-time parameter). But because of |
| * Returns: | ||
| * The value of the field or property method by reference | ||
| */ | ||
| ref Field refGetFeild(Field, Obj)(Obj obj, string name) if (is(Obj == class)) |
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.
Why Feild and not Field ? Is it a typo or am I missing something ?
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.
@bbasile That's just a type-o. It got fixed earlier, but the change got undone accidentally. It will be fixed again shortly.
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.
I see, anyway it seems that GH is malfunctioning right now, The doc builder displays Pull request SHA mismatch. Never seen that before.
|
@tsbockman I manually pulled your |
|
@JackStouffer The version I force-pushed about 10 minutes ago is the one you want. I'll make an actual pull request for it if GitHub doesn't acknowledge your manual pull soon. |
|
Pull request: JackStouffer#4 |
|
For some reason, I can't get the remote branch to sync to my local one despite the messages from git saying that everything is working. I am going to close this PR and use a new branch. I think this has something to do with the GH outage, but I don't know how this would be fixed if that is the case. |
|
@JackStouffer Just fork my |
|
It's not just this branch that seems to be not updating, my entire Phobos repo refuses to update at all :/ Hopefully this is just a GH backend problem that will be fixed soon, or else I might have to recreate my Phobos repo, which would mean having to close all of my open PRs. |
Added a new function
setAttribute, which allows someone to set an attribute of a type based by accessing the member with a string at runtime.This is useful when the member being changed can change due to some external factors. For example, I have a library that parses strings for timezone info. The string can contain information for regular UTC offsets or DST offsets, which are both attributes of an internal result type. The info on which to set is only available at runtime. A possible way to refactor such an example is to have a general offset variable and hold some state in the type as to which type of offset the value is referring to, but IMO this is much cleaner.