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.
Ah I see, so it still follows the same descriptor logic as anything else,
but _in addition_ if the value is a descriptor it will call `__get__`.


On Thu, Aug 14, 2014 at 3:58 PM, Kevin Modzelewski <notifications@github.com
> wrote:

> In src/runtime/objmodel.cpp:
>
> > @@ -807,28 +911,141 @@ Box* getattr_internal(Box* obj, const std::string& attr, bool check_cls, bool al
> >      }
> >
> >      if (obj->cls == type_cls) {
>
> I guess the terminology can be very ambiguous here; I mean that you have
> to check obj->cls for descriptors even if obj is itself a type object. In
> the case that obj is a type object, obj->cls is a metaclass which is why
> I called it that, though with respect to obj it is just the objects class
> (ie, I'm not referring to obj->cls->cls).
>
> For an example, in CPython, when looking up the __name__ attribute on a
> type, __name__ is a descriptor which has special __get__ behavior that
> looks up the embedded name of the class.
>
> >>> type.__name__
> 'type'
> >>> type.__dict__['__name__']
> <attribute '__name__' of 'type' objects>
> >>> type(type.__dict__['__name__'])
> <type 'getset_descriptor'>
> >>> type.__dict__['__name__'].__get__(object)
> 'object'
> >>> object.__name__
> 'object'
> >>> object.__dict__['__name__']
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> KeyError: '__name__'
>
> CPython implements this in type_getattro().
>
> —
> Reply to this email directly or view it on GitHub
> <https://github.com/dropbox/pyston/pull/123/files#r16272200>.
>