descriptor support for __get__ and __set__

Descriptor support for `__get__` and `__set__` in the `getattr`, `getclsattr`, and `callattr` paths.

- The `getattr` path follows the protocol described at https://docs.python.org/2/howto/descriptor.html. There are two cases: the object is an instance and the instance is a class.
  - For instances, It looks up attribute in the object's dictionary and the attribute from the class using `typeLookup`. In the latter, it looks for the `__get__` method, but if the attribute exists in _both_ it also checks that `__set__` exists.
  - For classes, it's simpler; it just looks if `__get__` exists in the looked-up item.
- The `getclsattr` path is sort of a mix - it looks up the attribute just in the class but will call `__get__` with the object rather than `None`.
- For `callattr`, the code for looking up the value seemed completely wrong (e.g., it was doing custom lookups through `__getattr__` before looking up the class object) so I scrapped that part. I figured this should basically share all logic with `getattr` or `getclsattr` as appropriate - except that it shouldn't wrap functions in instancemethods. So I refactored `getattr_internal` out into `getattr_internal_general` which takes an additional flag which species that it should not wrap functions, and `callattr` just calls that. (So `callattr` still does binding if necessary.) Similarly, I factored `clsattr_internal` into `clsattr_internal_general`.

One thing of note is that it currently doesn't rewrite the path where `callattr` calls `__get__`, since that involves _two_ function calls: one to `__get__` and another to the function that `__get__` returns. Of course, this path is basically just a getattr and a call, each of which could be written by itself, so this clearly isn't a fundamental limitation.

I got rid of `_handleClsAttr` which already had special-case descriptor logic for functions and member descriptors. Those cases are still handled specially though, just in getattr and getclsattr with the other descriptor logic.

Finally, for `setattr`, I check if `__set__` exists and if so call that.
oops, that should have been !isNondataDescriptorInstanceSpecialCase


On Mon, Aug 18, 2014 at 3:30 PM, Kevin Modzelewski <notifications@github.com
> wrote:

> In src/runtime/objmodel.cpp:
>
> > +    RewriterVarUsage r_get(RewriterVarUsage::empty());
> > +    if (descr) {
> > +        if (rewrite_args)
> > +            r_descr.addAttrGuard(BOX_CLS_OFFSET, (uint64_t)descr->cls);
> > +
> > +        // Special-case data descriptors (e.g., member descriptors)
> > +        Box* res = dataDescriptorInstanceSpecialCases(rewrite_args, obj, descr, r_descr, for_call, should_bind_out);
> > +        if (res) {
> > +            return res;
> > +        }
> > +
> > +        // Let's only check if __get__ exists if it's not a special case
> > +        // nondata descriptor. The nondata case is handled below, but
> > +        // we can immediately know to skip this part if it's one of the
> > +        // special case nondata descriptors.
> > +        if (!isDataDescriptorInstanceSpecialCase(descr)) {
>
> Shouldn't this check always be equivalent to the if (res) check?
>
> —
> Reply to this email directly or view it on GitHub
> <https://github.com/dropbox/pyston/pull/123/files#r16386479>.
>