Skip to content
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

pypy Event attribute raises AttributeError. Because tp_dictoffset. #419

Closed
illume opened this issue Mar 18, 2018 · 0 comments
Closed

pypy Event attribute raises AttributeError. Because tp_dictoffset. #419

illume opened this issue Mar 18, 2018 · 0 comments
Milestone

Comments

@illume
Copy link
Member

illume commented Mar 18, 2018

Here is info about the tp_dictoffset issue, and a solution at the end.

Test case.

from pygame.event import Event
e = Event(1, asdf=22)
print(e)
assert e.dict.get('asdf') == 22, 'Getting a dict attribute works.'
assert e.__dict__ is e.dict, 'the __dict__ is the same as this e.dict'
assert e.asdf == 22, 'Fails... events use the e.dict to make the attributes available.'
>>>> e = Event(1, asdf=22)
>>>> print(e)
<Event(1-ActiveEvent {'asdf': 22})>
>>>> assert e.dict.get('asdf') == 22, 'Getting a dict attribute works.'
>>>> assert e.__dict__ is e.dict, 'the __dict__ is the same as this e.dict'
>>>> assert e.asdf == 22, 'Fails... events use the e.dict to make the attributes available.'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Event' object has no attribute 'asdf'

where is the problem likely?

It overwrites the __dict__ variable of the object using tp_dictoffset(which pypy doesn't support at time of writing). See below.

Relevant documentation and code

src/event.c

Python docs.

tp_dict
tp_dictoffset
PyObject_GenericGetAttr

From src/event.c the type definition. We see that the object uses the tp_dictoffset

static PyTypeObject PyEvent_Type =
...
    offsetof(PyEventObject, dict),   /* tp_dictoffset */

Then we see that the event_members structure uses the same dict for __dict__.

#define OFF(x) offsetof(PyEventObject, x)

static PyMemberDef event_members[] = {
    {"__dict__",  T_OBJECT, OFF(dict), READONLY},
    {"type",      T_INT,    OFF(type), READONLY},
    {"dict",      T_OBJECT, OFF(dict), READONLY},
    {NULL}  /* Sentinel */
};

Here is the type definition: src/_pygame.h

typedef struct {
    PyObject_HEAD
    int type;
    PyObject* dict;
} PyEventObject;

pypy info

It seems that tp_dictoffset is not used in pypy/module/cpyext/parse/cpyext_object.h
PyObject_GenericGetAttr is defined in pypy/pypy/module/cpyext/object.py in there is a comment saying that it looks at __dict__.

There are a few places where tp_members is used.

How we fix it

[17:48:59]  <mattip>	illume: I doubt we can support overriding __dict__, but what is the exact error message and who is generating it?

Make our own getter and setter which tries the dict first.
pg_EventGetAttr, /* tp_getattro /
pg_EventSetAttr, /
tp_setattro */

Here is the getter and setter used to set the __dict__ as appropriate. From the #420 PR...

#ifdef PYPY_VERSION
/* Because pypy does not work with the __dict__ tp_dictoffset. */
PyObject* pg_EventGetAttr(PyObject *o, PyObject *attr_name) {
    /* Try e->dict first, if not try the generic attribute. */
    PyObject* result = PyDict_GetItem(((PyEventObject*)o)->dict, attr_name);
    if (!result) {
        return PyObject_GenericGetAttr(o, attr_name);
    }
    return result;
}

int pg_EventSetAttr(PyObject *o, PyObject *name, PyObject *value) {
    /* if the variable is in the dict, deal with it there.
       else if it's a normal attribute set it there.
       else if it's not an attribute, or in the dict, set it in the dict.
    */
    int dictResult;
    int setInDict = 0;
    PyObject* result = PyDict_GetItem(((PyEventObject*)o)->dict, name);

    if (result) {
        setInDict = 1;
    } else {
        result = PyObject_GenericGetAttr(o, name);
        if (!result) {
            setInDict = 1;
        }
    }

    if (setInDict) {
        dictResult = PyDict_SetItem(((PyEventObject*)o)->dict, name, value);
        if (dictResult) {
            return -1;
        }
        return 0;
    } else {
        return PyObject_GenericSetAttr(o, name, value);
    }
}
#endif
@illume illume added this to the pypy milestone Mar 18, 2018
@illume illume changed the title pypy Event attribute raises AttributeError pypy Event attribute raises AttributeError. Because tp_dictoffset. Mar 19, 2018
@illume illume modified the milestones: pypy, 1.9.4 Mar 19, 2018
@illume illume added the 1.9.4 label Mar 19, 2018
@illume illume modified the milestones: 1.9.4, pypy Mar 19, 2018
@illume illume closed this as completed Mar 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant