changeset 4599:89bec74fd793 v7.3.1047

updated for version 7.3.1047 Problem: Python: dir() does not work properly. Solution: Python patch 8. Add __dir__ method to all objects with custom tp_getattr supplemented by __members__ attribute for at least python-2* versions. __members__ is not mentioned in python-3* dir() output even if it is accessible. (ZyX)
author Bram Moolenaar <bram@vim.org>
date Wed, 29 May 2013 22:36:10 +0200
parents 18914d6c96c8
children 38944ed20efa
files src/if_py_both.h src/if_python.c src/if_python3.c src/testdir/test86.in src/testdir/test86.ok src/testdir/test87.in src/testdir/test87.ok src/version.c
diffstat 8 files changed, 260 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -117,6 +117,59 @@ StringToChars(PyObject *object, PyObject
     return (char_u *) p;
 }
 
+    static int
+add_string(PyObject *list, char *s)
+{
+    PyObject	*string;
+
+    if (!(string = PyString_FromString(s)))
+	return -1;
+    if (PyList_Append(list, string))
+    {
+	Py_DECREF(string);
+	return -1;
+    }
+
+    Py_DECREF(string);
+    return 0;
+}
+
+    static PyObject *
+ObjectDir(PyObject *self, char **attributes)
+{
+    PyMethodDef	*method;
+    char	**attr;
+    PyObject	*r;
+
+    if (!(r = PyList_New(0)))
+	return NULL;
+
+    if (self)
+	for (method = self->ob_type->tp_methods ; method->ml_name != NULL ; ++method)
+	    if (add_string(r, (char *) method->ml_name))
+	    {
+		Py_DECREF(r);
+		return NULL;
+	    }
+
+    for (attr = attributes ; *attr ; ++attr)
+	if (add_string(r, *attr))
+	{
+	    Py_DECREF(r);
+	    return NULL;
+	}
+
+#if PY_MAJOR_VERSION < 3
+    if (add_string(r, "__members__"))
+    {
+	Py_DECREF(r);
+	return NULL;
+    }
+#endif
+
+    return r;
+}
+
 /* Output buffer management
  */
 
@@ -132,6 +185,17 @@ typedef struct
     long error;
 } OutputObject;
 
+static char *OutputAttrs[] = {
+    "softspace",
+    NULL
+};
+
+    static PyObject *
+OutputDir(PyObject *self)
+{
+    return ObjectDir(self, OutputAttrs);
+}
+
     static int
 OutputSetattr(OutputObject *self, char *name, PyObject *val)
 {
@@ -291,6 +355,7 @@ static struct PyMethodDef OutputMethods[
     {"write",	    (PyCFunction)OutputWrite,		METH_VARARGS,	""},
     {"writelines",  (PyCFunction)OutputWritelines,	METH_VARARGS,	""},
     {"flush",	    (PyCFunction)OutputFlush,		METH_NOARGS,	""},
+    {"__dir__",	    (PyCFunction)OutputDir,		METH_NOARGS,	""},
     { NULL,	    NULL,				0,		NULL}
 };
 
@@ -826,6 +891,17 @@ DictionaryDestructor(DictionaryObject *s
     DESTRUCTOR_FINISH(self);
 }
 
+static char *DictionaryAttrs[] = {
+    "locked", "scope",
+    NULL
+};
+
+    static PyObject *
+DictionaryDir(PyObject *self)
+{
+    return ObjectDir(self, DictionaryAttrs);
+}
+
     static int
 DictionarySetattr(DictionaryObject *self, char *name, PyObject *val)
 {
@@ -985,7 +1061,8 @@ static PyMappingMethods DictionaryAsMapp
 
 static struct PyMethodDef DictionaryMethods[] = {
     {"keys",	(PyCFunction)DictionaryListKeys,	METH_NOARGS,	""},
-    { NULL,	NULL,					0,		NULL }
+    {"__dir__",	(PyCFunction)DictionaryDir,		METH_NOARGS,	""},
+    { NULL,	NULL,					0,		NULL}
 };
 
 static PyTypeObject ListType;
@@ -1331,6 +1408,17 @@ ListConcatInPlace(ListObject *self, PyOb
     return (PyObject *)(self);
 }
 
+static char *ListAttrs[] = {
+    "locked",
+    NULL
+};
+
+    static PyObject *
+ListDir(PyObject *self)
+{
+    return ObjectDir(self, ListAttrs);
+}
+
     static int
 ListSetattr(ListObject *self, char *name, PyObject *val)
 {
@@ -1368,8 +1456,9 @@ ListSetattr(ListObject *self, char *name
 }
 
 static struct PyMethodDef ListMethods[] = {
-    {"extend",	(PyCFunction)ListConcatInPlace,	METH_O,	""},
-    { NULL,	NULL,				0,	NULL }
+    {"extend",	(PyCFunction)ListConcatInPlace,	METH_O,		""},
+    {"__dir__",	(PyCFunction)ListDir,		METH_NOARGS,	""},
+    { NULL,	NULL,				0,		NULL}
 };
 
 typedef struct
@@ -1408,6 +1497,17 @@ FunctionDestructor(FunctionObject *self)
     DESTRUCTOR_FINISH(self);
 }
 
+static char *FunctionAttrs[] = {
+    "softspace",
+    NULL
+};
+
+    static PyObject *
+FunctionDir(PyObject *self)
+{
+    return ObjectDir(self, FunctionAttrs);
+}
+
     static PyObject *
 FunctionCall(FunctionObject *self, PyObject *argsObject, PyObject *kwargs)
 {
@@ -1472,8 +1572,9 @@ FunctionCall(FunctionObject *self, PyObj
 }
 
 static struct PyMethodDef FunctionMethods[] = {
-    {"__call__",    (PyCFunction)FunctionCall,	METH_VARARGS|METH_KEYWORDS, ""},
-    { NULL,	    NULL,			0,			   NULL}
+    {"__call__",(PyCFunction)FunctionCall,  METH_VARARGS|METH_KEYWORDS,	""},
+    {"__dir__",	(PyCFunction)FunctionDir,   METH_NOARGS,		""},
+    { NULL,	NULL,			0,				NULL}
 };
 
 /*
@@ -1842,6 +1943,17 @@ TabPageDestructor(TabPageObject *self)
     DESTRUCTOR_FINISH(self);
 }
 
+static char *TabPageAttrs[] = {
+    "windows", "number", "vars", "window", "valid",
+    NULL
+};
+
+    static PyObject *
+TabPageDir(PyObject *self)
+{
+    return ObjectDir(self, TabPageAttrs);
+}
+
     static PyObject *
 TabPageAttrValid(TabPageObject *self, char *name)
 {
@@ -1873,6 +1985,8 @@ TabPageAttr(TabPageObject *self, char *n
 	else
 	    return WindowNew(self->tab->tp_curwin, self->tab);
     }
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, TabPageAttrs);
     return NULL;
 }
 
@@ -1901,8 +2015,9 @@ TabPageRepr(TabPageObject *self)
 }
 
 static struct PyMethodDef TabPageMethods[] = {
-    /* name,	    function,		calling,    documentation */
-    { NULL,	    NULL,		0,	    NULL }
+    /* name,	    function,			calling,	documentation */
+    {"__dir__",	    (PyCFunction)TabPageDir,	METH_NOARGS,	""},
+    { NULL,	    NULL,			0,		NULL}
 };
 
 /*
@@ -2049,6 +2164,17 @@ get_firstwin(TabPageObject *tabObject)
     else
 	return firstwin;
 }
+static char *WindowAttrs[] = {
+    "buffer", "cursor", "height", "vars", "options", "number", "row", "col",
+    "tabpage", "valid",
+    NULL
+};
+
+    static PyObject *
+WindowDir(PyObject *self)
+{
+    return ObjectDir(self, WindowAttrs);
+}
 
     static PyObject *
 WindowAttrValid(WindowObject *self, char *name)
@@ -2103,9 +2229,8 @@ WindowAttr(WindowObject *self, char *nam
 	Py_INCREF(self->tabObject);
 	return (PyObject *)(self->tabObject);
     }
-    else if (strcmp(name,"__members__") == 0)
-	return Py_BuildValue("[ssssssssss]", "buffer", "cursor", "height",
-		"vars", "options", "number", "row", "col", "tabpage", "valid");
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, WindowAttrs);
     else
 	return NULL;
 }
@@ -2228,8 +2353,9 @@ WindowRepr(WindowObject *self)
 }
 
 static struct PyMethodDef WindowMethods[] = {
-    /* name,	    function,		calling,    documentation */
-    { NULL,	    NULL,		0,	    NULL }
+    /* name,	    function,			calling,	documentation */
+    {"__dir__",	    (PyCFunction)WindowDir,	METH_NOARGS,	""},
+    { NULL,	    NULL,			0,		NULL}
 };
 
 /*
@@ -3122,6 +3248,17 @@ RangeSlice(RangeObject *self, PyInt lo, 
     return RBSlice(self->buf, lo, hi, self->start, self->end);
 }
 
+static char *RangeAttrs[] = {
+    "start", "end",
+    NULL
+};
+
+    static PyObject *
+RangeDir(PyObject *self)
+{
+    return ObjectDir(self, RangeAttrs);
+}
+
     static PyObject *
 RangeAppend(RangeObject *self, PyObject *args)
 {
@@ -3162,7 +3299,8 @@ RangeRepr(RangeObject *self)
 static struct PyMethodDef RangeMethods[] = {
     /* name,	function,			calling,	documentation */
     {"append",	(PyCFunction)RangeAppend,	METH_VARARGS,	"Append data to the Vim range" },
-    { NULL,	NULL,				0,		NULL }
+    {"__dir__",	(PyCFunction)RangeDir,		METH_NOARGS,	""},
+    { NULL,	NULL,				0,		NULL}
 };
 
 static PyTypeObject BufferType;
@@ -3239,6 +3377,17 @@ BufferSlice(BufferObject *self, PyInt lo
     return RBSlice(self, lo, hi, 1, -1);
 }
 
+static char *BufferAttrs[] = {
+    "name", "number", "vars", "options", "valid",
+    NULL
+};
+
+    static PyObject *
+BufferDir(PyObject *self)
+{
+    return ObjectDir(self, BufferAttrs);
+}
+
     static PyObject *
 BufferAttrValid(BufferObject *self, char *name)
 {
@@ -3265,9 +3414,8 @@ BufferAttr(BufferObject *self, char *nam
     else if (strcmp(name, "options") == 0)
 	return OptionsNew(SREQ_BUF, self->buf, (checkfun) CheckBuffer,
 			(PyObject *) self);
-    else if (strcmp(name,"__members__") == 0)
-	return Py_BuildValue("[sssss]", "name", "number", "vars", "options",
-		"valid");
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, BufferAttrs);
     else
 	return NULL;
 }
@@ -3403,10 +3551,8 @@ static struct PyMethodDef BufferMethods[
     {"append",	    (PyCFunction)BufferAppend,	METH_VARARGS,	"Append data to Vim buffer" },
     {"mark",	    (PyCFunction)BufferMark,	METH_VARARGS,	"Return (row,col) representing position of named mark" },
     {"range",	    (PyCFunction)BufferRange,	METH_VARARGS,	"Return a range object which represents the part of the given buffer between line numbers s and e" },
-#if PY_VERSION_HEX >= 0x03000000
-    {"__dir__",	    (PyCFunction)BufferDir,	METH_NOARGS,	"List buffer attributes" },
-#endif
-    { NULL,	    NULL,			0,		NULL }
+    {"__dir__",	    (PyCFunction)BufferDir,	METH_NOARGS,	""},
+    { NULL,	    NULL,			0,		NULL}
 };
 
 /*
@@ -3538,6 +3684,17 @@ static PyMappingMethods BufMapAsMapping 
 /* Current items object
  */
 
+static char *CurrentAttrs[] = {
+    "buffer", "window", "line", "range", "tabpage",
+    NULL
+};
+
+    static PyObject *
+CurrentDir(PyObject *self)
+{
+    return ObjectDir(self, CurrentAttrs);
+}
+
     static PyObject *
 CurrentGetattr(PyObject *self UNUSED, char *name)
 {
@@ -3551,14 +3708,14 @@ CurrentGetattr(PyObject *self UNUSED, ch
 	return GetBufferLine(curbuf, (PyInt)curwin->w_cursor.lnum);
     else if (strcmp(name, "range") == 0)
 	return RangeNew(curbuf, RangeStart, RangeEnd);
-    else if (strcmp(name,"__members__") == 0)
-	return Py_BuildValue("[sssss]", "buffer", "window", "line", "range",
-		"tabpage");
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, CurrentAttrs);
     else
-    {
-	PyErr_SetString(PyExc_AttributeError, name);
+#if PY_MAJOR_VERSION < 3
+	return Py_FindMethod(WindowMethods, self, name);
+#else
 	return NULL;
-    }
+#endif
 }
 
     static int
@@ -3661,6 +3818,12 @@ CurrentSetattr(PyObject *self UNUSED, ch
     }
 }
 
+static struct PyMethodDef CurrentMethods[] = {
+    /* name,	    function,			calling,	documentation */
+    {"__dir__",	    (PyCFunction)CurrentDir,	METH_NOARGS,	""},
+    { NULL,	    NULL,			0,		NULL}
+};
+
     static void
 init_range_cmd(exarg_T *eap)
 {
@@ -4397,6 +4560,7 @@ init_structs(void)
     CurrentType.tp_basicsize = sizeof(CurrentObject);
     CurrentType.tp_flags = Py_TPFLAGS_DEFAULT;
     CurrentType.tp_doc = "vim current object";
+    CurrentType.tp_methods = CurrentMethods;
 #if PY_MAJOR_VERSION >= 3
     CurrentType.tp_getattro = (getattrofunc)CurrentGetattro;
     CurrentType.tp_setattro = (setattrofunc)CurrentSetattro;
--- a/src/if_python.c
+++ b/src/if_python.c
@@ -1066,6 +1066,8 @@ OutputGetattr(PyObject *self, char *name
 {
     if (strcmp(name, "softspace") == 0)
 	return PyInt_FromLong(((OutputObject *)(self))->softspace);
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, OutputAttrs);
 
     return Py_FindMethod(OutputMethods, self, name);
 }
@@ -1177,6 +1179,8 @@ RangeGetattr(PyObject *self, char *name)
 	return Py_BuildValue(Py_ssize_t_fmt, ((RangeObject *)(self))->start - 1);
     else if (strcmp(name, "end") == 0)
 	return Py_BuildValue(Py_ssize_t_fmt, ((RangeObject *)(self))->end - 1);
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, RangeAttrs);
     else
 	return Py_FindMethod(RangeMethods, self, name);
 }
@@ -1396,6 +1400,8 @@ DictionaryGetattr(PyObject *self, char *
 	return PyInt_FromLong(this->dict->dv_lock);
     else if (strcmp(name, "scope") == 0)
 	return PyInt_FromLong(this->dict->dv_scope);
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, DictionaryAttrs);
 
     return Py_FindMethod(DictionaryMethods, self, name);
 }
@@ -1420,6 +1426,8 @@ ListGetattr(PyObject *self, char *name)
 {
     if (strcmp(name, "locked") == 0)
 	return PyInt_FromLong(((ListObject *)(self))->list->lv_lock);
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, ListAttrs);
 
     return Py_FindMethod(ListMethods, self, name);
 }
@@ -1431,6 +1439,8 @@ FunctionGetattr(PyObject *self, char *na
 
     if (strcmp(name, "name") == 0)
 	return PyString_FromString((char *)(this->name));
+    else if (strcmp(name, "__members__") == 0)
+	return ObjectDir(NULL, FunctionAttrs);
     else
 	return Py_FindMethod(FunctionMethods, self, name);
 }
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -666,7 +666,6 @@ call_PyType_GenericAlloc(PyTypeObject *t
     return PyType_GenericAlloc(type,nitems);
 }
 
-static PyObject *BufferDir(PyObject *);
 static PyObject *OutputGetattro(PyObject *, PyObject *);
 static int OutputSetattro(PyObject *, PyObject *, PyObject *);
 static PyObject *BufferGetattro(PyObject *, PyObject *);
@@ -1094,14 +1093,6 @@ BufferSetattro(PyObject *self, PyObject 
     return BufferSetattr((BufferObject *)(self), name, val);
 }
 
-    static PyObject *
-BufferDir(PyObject *self UNUSED)
-{
-    return Py_BuildValue("[ssssssss]",
-	    "name", "number", "vars", "options", "valid",
-	    "append", "mark", "range");
-}
-
 /******************/
 
     static PyObject *
@@ -1368,8 +1359,11 @@ static PySequenceMethods WinListAsSeq = 
     static PyObject *
 CurrentGetattro(PyObject *self, PyObject *nameobj)
 {
+    PyObject	*r;
     GET_ATTR_STRING(name, nameobj);
-    return CurrentGetattr(self, name);
+    if (!(r = CurrentGetattr(self, name)))
+	return PyObject_GenericGetAttr(self, nameobj);
+    return r;
 }
 
     static int
--- a/src/testdir/test86.in
+++ b/src/testdir/test86.in
@@ -691,6 +691,24 @@ for expr, attr in (
     cb.append(expr + ':' + attr + ':' + repr(type(eval(expr)) is getattr(vim, attr)))
 EOF
 :"
+:" Test __dir__() method
+py << EOF
+for name, o in (
+        ('current',    vim.current),
+        ('buffer',     vim.current.buffer),
+        ('window',     vim.current.window),
+        ('tabpage',    vim.current.tabpage),
+        ('range',      vim.current.range),
+        ('dictionary', vim.bindeval('{}')),
+        ('list',       vim.bindeval('[]')),
+        ('function',   vim.bindeval('function("tr")')),
+        ('output',     sys.stdout),
+    ):
+    cb.append(name + ':' + ','.join(dir(o)))
+del name
+del o
+EOF
+:"
 :" Test exceptions
 :fun Exe(e)
 :   execute a:e
--- a/src/testdir/test86.ok
+++ b/src/testdir/test86.ok
@@ -382,6 +382,15 @@ vim.current.buffer:Buffer:True
 vim.current.range:Range:True
 vim.current.window:Window:True
 vim.current.tabpage:TabPage:True
+current:__dir__,__members__,buffer,line,range,tabpage,window
+buffer:__dir__,__members__,append,mark,name,number,options,range,valid,vars
+window:__dir__,__members__,buffer,col,cursor,height,number,options,row,tabpage,valid,vars
+tabpage:__dir__,__members__,number,valid,vars,window,windows
+range:__dir__,__members__,append,end,start
+dictionary:__dir__,__members__,keys,locked,scope
+list:__dir__,__members__,extend,locked
+function:__call__,__dir__,__members__,softspace
+output:__dir__,__members__,flush,softspace,write,writelines
 (<class 'vim.error'>, error('abc',))
 (<class 'vim.error'>, error('def',))
 (<class 'vim.error'>, error('ghi',))
--- a/src/testdir/test87.in
+++ b/src/testdir/test87.in
@@ -669,6 +669,24 @@ for expr, attr in (
     cb.append(expr + ':' + attr + ':' + repr(type(eval(expr)) is getattr(vim, attr)))
 EOF
 :"
+:" Test __dir__() method
+py3 << EOF
+for name, o in (
+        ('current',    vim.current),
+        ('buffer',     vim.current.buffer),
+        ('window',     vim.current.window),
+        ('tabpage',    vim.current.tabpage),
+        ('range',      vim.current.range),
+        ('dictionary', vim.bindeval('{}')),
+        ('list',       vim.bindeval('[]')),
+        ('function',   vim.bindeval('function("tr")')),
+        ('output',     sys.stdout),
+    ):
+    cb.append(name + ':' + ','.join(dir(o)))
+del name
+del o
+EOF
+:"
 :" Test exceptions
 :fun Exe(e)
 :   execute a:e
--- a/src/testdir/test87.ok
+++ b/src/testdir/test87.ok
@@ -371,6 +371,15 @@ vim.current.buffer:Buffer:True
 vim.current.range:Range:True
 vim.current.window:Window:True
 vim.current.tabpage:TabPage:True
+current:__dir__,buffer,line,range,tabpage,window
+buffer:__dir__,append,mark,name,number,options,range,valid,vars
+window:__dir__,buffer,col,cursor,height,number,options,row,tabpage,valid,vars
+tabpage:__dir__,number,valid,vars,window,windows
+range:__dir__,append,end,start
+dictionary:__dir__,keys,locked,scope
+list:__dir__,extend,locked
+function:__call__,__dir__,softspace
+output:__dir__,flush,softspace,write,writelines
 (<class 'vim.error'>, error('abc',))
 (<class 'vim.error'>, error('def',))
 (<class 'vim.error'>, error('ghi',))
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1047,
+/**/
     1046,
 /**/
     1045,