Index: source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h =================================================================== --- source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -69,6 +69,7 @@ Dictionary, List, String, + Module, File }; @@ -185,15 +186,6 @@ return result; } - PyObjectType - GetObjectType() const; - - PythonString - Repr (); - - PythonString - Str (); - PythonObject & operator=(const PythonObject &other) { @@ -201,6 +193,21 @@ return *this; } + PyObjectType + GetObjectType() const; + + PythonString + Repr() const; + + PythonString + Str() const; + + static PythonObject + ResolveNameGlobal(llvm::StringRef name); + + PythonObject + ResolveName(llvm::StringRef name) const; + bool HasAttribute(llvm::StringRef attribute) const; @@ -224,7 +231,8 @@ return T(PyRefType::Borrowed, m_py_obj); } - StructuredData::ObjectSP CreateStructuredObject() const; + StructuredData::ObjectSP + CreateStructuredObject() const; protected: PyObject* m_py_obj; @@ -338,6 +346,34 @@ StructuredData::DictionarySP CreateStructuredDictionary() const; }; +class PythonModule : public PythonObject +{ + public: + PythonModule(); + PythonModule(PyRefType type, PyObject *o); + PythonModule(const PythonModule &dict); + + ~PythonModule() override; + + static bool Check(PyObject *py_obj); + + static PythonModule MainModule(); + + // Bring in the no-argument base class version + using PythonObject::Reset; + + void Reset(PyRefType type, PyObject *py_obj) override; + + PythonDictionary GetDictionary() const; + + template + T + ResolveNameAs(llvm::StringRef name) const + { + return ResolveName(name).AsType(); + } +}; + class PythonFile : public PythonObject { public: Index: source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp =================================================================== --- source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -67,6 +67,8 @@ if (!IsAllocated()) return PyObjectType::None; + if (PythonModule::Check(m_py_obj)) + return PyObjectType::Module; if (PythonList::Check(m_py_obj)) return PyObjectType::List; if (PythonDictionary::Check(m_py_obj)) @@ -81,7 +83,7 @@ } PythonString -PythonObject::Repr() +PythonObject::Repr() const { if (!m_py_obj) return PythonString(); @@ -92,7 +94,7 @@ } PythonString -PythonObject::Str() +PythonObject::Str() const { if (!m_py_obj) return PythonString(); @@ -102,6 +104,43 @@ return PythonString(PyRefType::Owned, str); } +PythonObject +PythonObject::ResolveNameGlobal(llvm::StringRef name) +{ + return PythonModule::MainModule().ResolveName(name); +} + +PythonObject +PythonObject::ResolveName(llvm::StringRef name) const +{ + // Resolve the name in the context of the specified object. If, + // for example, `this` refers to a PyModule, then this will look for + // `name` in this module. If `this` refers to a PyType, then it will + // resolve `name` as an attribute of that type. If `this` refers to + // an instance of an object, then it will resolve `name` as the value + // of the specified field. + // + // This function handles dotted names so that, for example, if `m_py_obj` + // refers to the `sys` module, and `name` == "path.append", then it + // will find the function `sys.path.append`. + + size_t dot_pos = name.find_first_of('.'); + if (dot_pos == llvm::StringRef::npos) + { + // No dots in the name, we should be able to find the value immediately + // as an attribute of `use_object`. + return GetAttributeValue(name); + } + + // Look up the first piece of the name, and resolve the rest as a child of that. + PythonObject parent = ResolveName(name.substr(0, dot_pos)); + if (!parent.IsAllocated()) + return PythonObject(); + + // Tail recursion.. should be optimized by the compiler + return parent.ResolveName(name.substr(dot_pos + 1)); +} + bool PythonObject::HasAttribute(llvm::StringRef attr) const { @@ -605,6 +644,62 @@ return result; } +PythonModule::PythonModule() : PythonObject() +{ +} + +PythonModule::PythonModule(PyRefType type, PyObject *py_obj) +{ + Reset(type, py_obj); // Use "Reset()" to ensure that py_obj is a module +} + +PythonModule::PythonModule(const PythonModule &dict) : PythonObject(dict) +{ +} + +PythonModule::~PythonModule() +{ +} + +PythonModule +PythonModule::MainModule() +{ + return PythonModule(PyRefType::Borrowed, PyImport_AddModule("__main__")); +} + +bool +PythonModule::Check(PyObject *py_obj) +{ + if (!py_obj) + return false; + + return PyModule_Check(py_obj); +} + +void +PythonModule::Reset(PyRefType type, PyObject *py_obj) +{ + // Grab the desired reference type so that if we end up rejecting + // `py_obj` it still gets decremented if necessary. + PythonObject result(type, py_obj); + + if (!PythonModule::Check(py_obj)) + { + PythonObject::Reset(); + return; + } + + // Calling PythonObject::Reset(const PythonObject&) will lead to stack overflow since it calls + // back into the virtual implementation. + PythonObject::Reset(PyRefType::Borrowed, result.get()); +} + +PythonDictionary +PythonModule::GetDictionary() const +{ + return PythonDictionary(PyRefType::Borrowed, PyModule_GetDict(m_py_obj)); +} + PythonFile::PythonFile() : PythonObject() { Index: unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp =================================================================== --- unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp +++ unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp @@ -96,6 +96,66 @@ EXPECT_EQ(original_refcnt + 1, borrowed_long.get()->ob_refcnt); } +TEST_F(PythonDataObjectsTest, TestGlobalNameResolutionNoDot) +{ + PythonObject sys_module = PythonObject::ResolveNameGlobal("sys"); + EXPECT_TRUE(sys_module.IsAllocated()); + EXPECT_TRUE(PythonModule::Check(sys_module.get())); +} + +TEST_F(PythonDataObjectsTest, TestModuleNameResolutionNoDot) +{ + PythonObject sys_module = PythonObject::ResolveNameGlobal("sys"); + PythonObject sys_path = sys_module.ResolveName("path"); + PythonObject sys_version_info = sys_module.ResolveName("version_info"); + EXPECT_TRUE(sys_path.IsAllocated()); + EXPECT_TRUE(sys_version_info.IsAllocated()); + + EXPECT_TRUE(PythonList::Check(sys_path.get())); +} + +TEST_F(PythonDataObjectsTest, TestTypeNameResolutionNoDot) +{ + PythonObject sys_module = PythonObject::ResolveNameGlobal("sys"); + PythonObject sys_version_info = sys_module.ResolveName("version_info"); + + PythonObject version_info_type(PyRefType::Owned, PyObject_Type(sys_version_info.get())); + EXPECT_TRUE(version_info_type.IsAllocated()); + PythonObject major_version_field = version_info_type.ResolveName("major"); + EXPECT_TRUE(major_version_field.IsAllocated()); +} + +TEST_F(PythonDataObjectsTest, TestInstanceNameResolutionNoDot) +{ + PythonObject sys_module = PythonObject::ResolveNameGlobal("sys"); + PythonObject sys_version_info = sys_module.ResolveName("version_info"); + PythonObject major_version_field = sys_version_info.ResolveName("major"); + PythonObject minor_version_field = sys_version_info.ResolveName("minor"); + + EXPECT_TRUE(major_version_field.IsAllocated()); + EXPECT_TRUE(minor_version_field.IsAllocated()); + + PythonInteger major_version_value = major_version_field.AsType(); + PythonInteger minor_version_value = minor_version_field.AsType(); + + EXPECT_EQ(PY_MAJOR_VERSION, major_version_value.GetInteger()); + EXPECT_EQ(PY_MINOR_VERSION, minor_version_value.GetInteger()); +} + +TEST_F(PythonDataObjectsTest, TestGlobalNameResolutionWithDot) +{ + PythonObject sys_path = PythonObject::ResolveNameGlobal("sys.path"); + EXPECT_TRUE(sys_path.IsAllocated()); + EXPECT_TRUE(PythonList::Check(sys_path.get())); + + PythonInteger version_major = PythonObject::ResolveNameGlobal("sys.version_info.major").AsType(); + PythonInteger version_minor = PythonObject::ResolveNameGlobal("sys.version_info.minor").AsType(); + EXPECT_TRUE(version_major.IsAllocated()); + EXPECT_TRUE(version_minor.IsAllocated()); + EXPECT_EQ(PY_MAJOR_VERSION, version_major.GetInteger()); + EXPECT_EQ(PY_MINOR_VERSION, version_minor.GetInteger()); +} + TEST_F(PythonDataObjectsTest, TestPythonInteger) { // Test that integers behave correctly when wrapped by a PythonInteger.