Commit b4dba674 authored by Sven Brauch's avatar Sven Brauch

Initial implementation of the "correction files" feature

The idea is to have small, user-written or shipped files which correct
glaring "errors" in library code without shipping a maintenance-heavy,
ugly modified copy of the library code. So far, you can specify function
return types only, but more features are easy to add now.
parent 4f343ace
......@@ -78,4 +78,5 @@ install(TARGETS kdevpythonlanguagesupport DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES kdevpythonsupport.desktop DESTINATION ${SERVICES_INSTALL_DIR})
install(DIRECTORY documentation_files DESTINATION ${DATA_INSTALL_DIR}/kdevpythonsupport)
install(DIRECTORY correction_files DESTINATION ${DATA_INSTALL_DIR}/kdevpythonsupport)
install(DIRECTORY python-src/Lib/encodings DESTINATION ${DATA_INSTALL_DIR}/kdevpythonsupport/encodings)
# This file is used to unit-test the "correction files" feature,
# and can serve as an example of the capabilities of this feature.
def function_global_func():
returns = int()
......@@ -24,6 +24,8 @@ set(duchain_SRCS
navigation/navigationwidget.cpp
navigation/declarationnavigationcontext.cpp
correctionhelper.cpp
assistants/missingincludeassistant.cpp
../docfilekcm/docfilewizard.cpp # for the docfile generation assistant widget, to be used in the problem resolver
)
......
/*
* <one line to give the library's name and an idea of what it does.>
* Copyright 2013 Sven Brauch <svenbrauch@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "correctionhelper.h"
#include "helpers.h"
#include <language/duchain/duchain.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/declaration.h>
#include <KStandardDirs>
#include <QFile>
namespace Python {
CorrectionHelper::CorrectionHelper(const IndexedString& _url)
{
m_contextStack.push(0);
KUrl url(_url.toUrl());
QString path;
foreach ( const KUrl& basePath, Helper::getSearchPaths(KUrl()) ) {
if ( basePath.isParentOf(url) ) {
path = KUrl::relativePath(basePath.path(), url.path());
break;
}
}
if ( path.isEmpty() ) {
return;
}
static QString baseDirectory;
if ( baseDirectory.isEmpty() ) {
baseDirectory = KStandardDirs::locate("data", "kdevpythonsupport/correction_files/");
}
KUrl absolutePath = KUrl(baseDirectory + path);
absolutePath.cleanPath();
if ( ! QFile::exists(absolutePath.path()) ) {
return;
}
DUChainReadLocker lock;
m_hintTopContext = DUChain::self()->chainForDocument(IndexedString(absolutePath));
kDebug() << "got top context for" << url << path << absolutePath << m_hintTopContext;
m_contextStack.top() = m_hintTopContext.data();
if ( ! m_hintTopContext ) {
// The file exists, but was not parsed yet. Schedule it, and re-schedule the current one too.
// TODO
}
}
CorrectionHelper::~CorrectionHelper()
{
Q_ASSERT(m_contextStack.size() == 1);
Q_ASSERT(m_contextStack.top() == m_hintTopContext.data());
}
void CorrectionHelper::enter(const KDevelop::Identifier& identifier)
{
DUContext* current = m_contextStack.top();
if ( ! current ) {
// no hints for the parent object, so no hints for its children either
m_contextStack.push(0);
return;
}
DUChainReadLocker lock;
const QList<Declaration*> decls = current->findDeclarations(identifier);
if ( decls.isEmpty() ) {
// no hints for the current object
m_contextStack.push(0);
return;
}
// there's a hint declaration for this object, put it on the stack
DUContext* internal = decls.first()->internalContext();
m_contextStack.push(internal);
return;
}
AbstractType::Ptr CorrectionHelper::hintForLocal(const QString &local) const
{
return hintFor(KDevelop::Identifier(QLatin1String("l_") + local));
}
AbstractType::Ptr CorrectionHelper::returnTypeHint() const
{
AbstractType::Ptr result = hintFor(KDevelop::Identifier(QLatin1String("returns")));
DUChainReadLocker lock;
kDebug() << "return type hint requested, result:" << ( result ? result->toString() : "none" );
return result;
}
AbstractType::Ptr CorrectionHelper::hintFor(const KDevelop::Identifier &identifier) const
{
DUContext* current = m_contextStack.top();
AbstractType::Ptr hint;
if ( ! current ) {
return hint;
}
const QList<Declaration*> decls = current->findDeclarations(identifier);
if ( decls.isEmpty() ) {
return hint;
}
return decls.first()->abstractType();
}
CorrectionHelper::Recursion CorrectionHelper::enterClass(const QString& identifier)
{
enter(KDevelop::Identifier(QLatin1String("class_") + identifier));
return CorrectionHelper::Recursion(this);
}
CorrectionHelper::Recursion CorrectionHelper::enterFunction(const QString &identifier)
{
enter(KDevelop::Identifier(QLatin1String("function_") + identifier));
return CorrectionHelper::Recursion(this);
}
void CorrectionHelper::leave()
{
m_contextStack.pop();
}
}
\ No newline at end of file
/*
* <one line to give the library's name and an idea of what it does.>
* Copyright 2013 Sven Brauch <svenbrauch@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CORRECTIONHELPER_H
#define CORRECTIONHELPER_H
#include <language/duchain/types/abstracttype.h>
#include <language/duchain/ducontext.h>
#include <language/duchain/topducontext.h>
#include <KUrl>
using namespace KDevelop;
namespace Python {
class CorrectionHelper
{
public:
CorrectionHelper(const IndexedString& url);
virtual ~CorrectionHelper();
/// Keep this object alive as long as you are parsing a class or function.
/// On destruction, it will automatically leave that class or function.
struct Recursion {
Recursion(CorrectionHelper* h) : m_helper(h) { };
~Recursion() {
m_helper->leave();
};
CorrectionHelper* m_helper;
};
Recursion enterClass(const QString& identifier);
Recursion enterFunction(const QString& identifier);
AbstractType::Ptr hintForLocal(const QString& local) const;
AbstractType::Ptr returnTypeHint() const;
private:
AbstractType::Ptr hintFor(const KDevelop::Identifier& identifier) const;
void enter(const Identifier& identifier);
void leave();
ReferencedTopDUContext m_hintTopContext;
QStack<DUContext*> m_contextStack;
};
} // namespace Python
#endif // CORRECTIONHELPER_H
......@@ -81,6 +81,8 @@ void DeclarationBuilder::setPrebuilding(bool prebuilding)
ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, Ast* node, ReferencedTopDUContext updateContext)
{
m_correctionHelper.reset(new CorrectionHelper(url));
// The declaration builder needs to run twice, so it can resolve uses of e.g. functions
// which are called before they are defined (which is easily possible, due to python's dynamic nature).
if ( ! m_prebuilding ) {
......@@ -240,6 +242,11 @@ template<typename T> T* DeclarationBuilder::visitVariableDeclaration(Identifier*
Ast* rangeNode = originalAst ? originalAst : node;
RangeInRevision range = editorFindRange(rangeNode, rangeNode);
// ask the correction file library if there's a user-specified type for this object
if ( AbstractType::Ptr hint = m_correctionHelper->hintForLocal(node->value) ) {
type = hint;
}
// If no type is known, display "mixed".
if ( ! type ) {
type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed));
......@@ -1365,6 +1372,8 @@ void DeclarationBuilder::visitAssignment(AssignmentAst* node)
void DeclarationBuilder::visitClassDefinition( ClassDefinitionAst* node )
{
const CorrectionHelper::Recursion r(m_correctionHelper->enterClass(node->name->value));
StructureType::Ptr type(new StructureType());
DUChainWriteLocker lock(DUChain::lock());
......@@ -1479,6 +1488,8 @@ template<typename T> void DeclarationBuilder::visitDecorators(QList< Python::Exp
void DeclarationBuilder::visitFunctionDefinition( FunctionDefinitionAst* node )
{
const CorrectionHelper::Recursion r(m_correctionHelper->enterFunction(node->name->value));
// Search for an eventual containing class declaration;
// if that exists, then this function is a member function
DeclarationPointer eventualParentDeclaration(currentDeclaration());
......@@ -1565,6 +1576,11 @@ void DeclarationBuilder::visitFunctionDefinition( FunctionDefinitionAst* node )
m_firstAttributeDeclaration = DeclarationPointer(0);
dec->setStatic(true);
}
if ( AbstractType::Ptr hint = m_correctionHelper->returnTypeHint() ) {
type->setReturnType(hint);
dec->setType<FunctionType>(type);
}
// check for documentation
dec->setComment(getDocstring(node->body));
......
......@@ -29,6 +29,7 @@
#include "contextbuilder.h"
#include "typebuilder.h"
#include "correctionhelper.h"
#include <language/duchain/types/unsuretype.h>
namespace Python
......@@ -163,6 +164,7 @@ protected:
DeclarationPointer m_firstAttributeDeclaration;
private:
QList<DUChainBase*> m_scheduledForDeletion;
QScopedPointer<CorrectionHelper> m_correctionHelper;
QString buildModuleNameFromNode(ImportFromAst* node, AliasAst* alias, const QString& intermediate);
};
......
def global_func():
return
\ No newline at end of file
......@@ -115,8 +115,18 @@ void PyDUChainTest::init()
kDebug() << "Searching for python files in " << assetModuleDir.absolutePath();
// sorry, but this is the easiest way to do it ;)
QList<KUrl> oldPaths = Helper::cachedSearchPaths;
Helper::cachedSearchPaths = QList<KUrl>() << KUrl(assetsDir.absolutePath());
QList<QString> foundfiles = FindPyFiles(assetModuleDir);
QString correctionFileDir = KStandardDirs::locate("data", "kdevpythonsupport/correction_files/");
KUrl correctionFileUrl = KUrl(correctionFileDir + "testCorrectionFiles/example.py");
correctionFileUrl.cleanPath();
foundfiles.prepend(correctionFileUrl.path());
Helper::cachedSearchPaths = oldPaths;
foreach(const QString filename, foundfiles) {
kDebug() << "Parsing asset: " << filename;
DUChain::self()->updateContextForUrl(IndexedString(filename), KDevelop::TopDUContext::AllDeclarationsContextsAndUses);
......@@ -1093,3 +1103,27 @@ void PyDUChainTest::testContainerTypes_data()
QTest::newRow("list_append") << "d = []\nd.append(3)\ncheckme = d[0]" << "int" << true;
}
void PyDUChainTest::testCorrectionFiles()
{
QFETCH(QString, code);
QFETCH(QString, expectedType);
ReferencedTopDUContext ctx = parse(code.toAscii());
QVERIFY(ctx);
DUChainReadLocker lock;
QList<Declaration*> decls = ctx->findDeclarations(QualifiedIdentifier("checkme"));
QVERIFY(decls.length() > 0);
QVERIFY(decls.first()->abstractType());
QCOMPARE(decls.first()->abstractType()->toString(), expectedType);
}
void PyDUChainTest::testCorrectionFiles_data()
{
QTest::addColumn<QString>("code");
QTest::addColumn<QString>("expectedType");
QTest::newRow("global_scope_return_type") << "from testCorrectionFiles.example import global_func\n"
"checkme = global_func()" << "int";
}
......@@ -85,6 +85,8 @@ class PyDUChainTest : public QObject
void testCannotOverwriteBuiltins_data();
void testOperators();
void testOperators_data();
void testCorrectionFiles();
void testCorrectionFiles_data();
private:
QList<KDevelop::TestFile*> createdFiles;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment