diff --git a/duchain/expressionvisitor.h b/duchain/expressionvisitor.h --- a/duchain/expressionvisitor.h +++ b/duchain/expressionvisitor.h @@ -87,8 +87,8 @@ * @param classDecl The call's class declaration, if any * @param isConstructor whether a constructor is being called */ - void checkForDecorators(CallAst* node, Python::FunctionDeclaration* funcDecl, - Python::ClassDeclaration* classDecl, bool isConstructor); + AbstractType::Ptr docstringTypeOverride(CallAst* node, const AbstractType::Ptr normalType, + const QString& docstring); bool isAlias() const { return m_isAlias; diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -115,75 +115,68 @@ foreach ( ExpressionAst* c, node->arguments ) { AstDefaultVisitor::visitNode(c); } - ExpressionVisitor v(this); v.visitNode(node->function); - Declaration* actualDeclaration = 0; - FunctionType::Ptr unidentifiedFunctionType; - if ( ! v.m_isAlias && v.lastType() && v.lastType()->whichType() == AbstractType::TypeFunction ) { - unidentifiedFunctionType = v.lastType().cast(); - } - else if ( ! v.m_isAlias && v.lastType() && v.lastType()->whichType() == AbstractType::TypeStructure ) { - // use __call__ - DUChainReadLocker lock; - auto c = v.lastType().cast()->internalContext(topContext()); - if ( c ) { - auto decls = c->findDeclarations(QualifiedIdentifier("__call__")); - if ( ! decls.isEmpty() ) { - auto decl = dynamic_cast(decls.first()); - if ( decl ) { - unidentifiedFunctionType = decl->abstractType().cast(); - } + if ( ! v.isAlias() && v.lastType() ) { + if ( v.lastType()->whichType() == AbstractType::TypeFunction ) { + encounter(v.lastType().cast()->returnType()); + return; + } + else if ( v.lastType()->whichType() == AbstractType::TypeStructure ) { + static const IndexedIdentifier callId(KDevelop::Identifier("__call__")); + DUChainReadLocker lock; + auto attr = Helper::accessAttribute(v.lastType(), callId, topContext()); + attr = dynamic_cast(attr); + if (attr) { + encounter(attr->type()->returnType()); + return; } } } - else { - actualDeclaration = v.lastDeclaration().data(); - } - - if ( unidentifiedFunctionType ) { - encounter(unidentifiedFunctionType->returnType()); + auto declaration = Helper::resolveAliasDeclaration(v.lastDeclaration().data()); + if ( ! declaration ) { + encounterUnknown(); return; } - else if ( !actualDeclaration ) { - setConfident(false); - return encounterUnknown(); - } - + ClassDeclaration* classDecl = dynamic_cast(declaration); DUChainReadLocker lock; - actualDeclaration = Helper::resolveAliasDeclaration(actualDeclaration); - ClassDeclaration* classDecl = dynamic_cast(actualDeclaration); - auto function = Helper::functionForCalled(actualDeclaration); + auto function = Helper::functionForCalled(declaration, v.isAlias()); lock.unlock(); - if ( function.declaration && function.declaration->type() ) { - // try to deduce type from a decorator - checkForDecorators(node, function.declaration, classDecl, function.isConstructor); + AbstractType::Ptr type; + Declaration* decl; + + if ( function.isConstructor && classDecl ) { + // Don't use return type from constructor. + // It's wrong for builtins, or classes without their own __init__ methods(). + type = classDecl->abstractType(); + decl = classDecl; } - else if ( classDecl ) { - return encounter(classDecl->abstractType(), DeclarationPointer(classDecl)); + else if ( function.declaration && function.declaration->type() ) { + // But do use the return value of normal functions or __call__(). + type = function.declaration->type()->returnType(); + decl = function.declaration; } else { - if ( actualDeclaration ) { - qCDebug(KDEV_PYTHON_DUCHAIN) << "Declaraton is not a class or function declaration"; + qCDebug(KDEV_PYTHON_DUCHAIN) << "Declaration is not a class or function declaration"; + encounterUnknown(); + return; + } + if ( function.declaration ) { + auto docstring = function.declaration->comment(); + if ( ! docstring.isEmpty() ) { + // Our documentation data uses special docstrings that override the return type + // of some functions (including constructors). + type = docstringTypeOverride(node, type, docstring); } - return encounterUnknown(); } + encounter(type, DeclarationPointer(decl)); } -void ExpressionVisitor::checkForDecorators(CallAst* node, FunctionDeclaration* funcDecl, ClassDeclaration* classDecl, bool isConstructor) +AbstractType::Ptr ExpressionVisitor::docstringTypeOverride( + CallAst* node, const AbstractType::Ptr normalType, const QString& docstring) { - AbstractType::Ptr type; - Declaration* useDeclaration = nullptr; - if ( isConstructor && classDecl ) { - type = classDecl->abstractType(); - useDeclaration = classDecl; - } - else { - type = funcDecl->type()->returnType(); - useDeclaration = funcDecl; - } - + auto docstringType = normalType; auto listOfTuples = [&](AbstractType::Ptr key, AbstractType::Ptr value) { auto newType = typeObjectForIntegralType("list"); IndexedContainer::Ptr newContents = typeObjectForIntegralType("tuple"); @@ -214,8 +207,7 @@ baseTypeVisitor.visitNode(static_cast(node->function)->value); if ( auto t = baseTypeVisitor.lastType().cast() ) { qCDebug(KDEV_PYTHON_DUCHAIN) << "Found container, using type"; - AbstractType::Ptr newType = t->contentType().abstractType(); - encounter(newType, DeclarationPointer(useDeclaration)); + docstringType = t->contentType().abstractType(); return true; } return false; @@ -243,8 +235,7 @@ contentType = map->keyType().abstractType(); } newType->addContentType(contentType); - AbstractType::Ptr resultingType = newType.cast(); - encounter(resultingType, DeclarationPointer(useDeclaration)); + docstringType = newType.cast(); return true; } return false; @@ -261,8 +252,7 @@ DUChainWriteLocker lock; auto intType = typeObjectForIntegralType("int"); auto enumerated = enumeratedTypeVisitor.lastType(); - auto result = listOfTuples(intType, Helper::contentOfIterable(enumerated)); - encounter(result, DeclarationPointer(useDeclaration)); + docstringType = listOfTuples(intType, Helper::contentOfIterable(enumerated)); return true; }; @@ -277,8 +267,7 @@ DUChainWriteLocker lock; if ( auto t = baseTypeVisitor.lastType().cast() ) { qCDebug(KDEV_PYTHON_DUCHAIN) << "Got container:" << t->toString(); - auto resultingType = listOfTuples(t->keyType().abstractType(), t->contentType().abstractType()); - encounter(resultingType, DeclarationPointer(useDeclaration)); + docstringType = listOfTuples(t->keyType().abstractType(), t->contentType().abstractType()); return true; } return false; @@ -297,7 +286,7 @@ return false; } ListType::Ptr realTarget; - if ( auto target = ListType::Ptr::dynamicCast(type) ) { + if ( auto target = ListType::Ptr::dynamicCast(normalType) ) { realTarget = target; } if ( auto source = ListType::Ptr::dynamicCast(v.lastType()) ) { @@ -308,7 +297,7 @@ auto newType = ListType::Ptr::staticCast(AbstractType::Ptr(realTarget->clone())); Q_ASSERT(newType); newType->addContentType(source->contentType().abstractType()); - encounter(AbstractType::Ptr::staticCast(newType), DeclarationPointer(useDeclaration)); + docstringType = AbstractType::Ptr::staticCast(newType); return true; } return false; @@ -316,18 +305,18 @@ foreach ( const QString& currentHint, knownDecoratorHints.keys() ) { QStringList arguments; - if ( ! Helper::docstringContainsHint(funcDecl, currentHint, &arguments) ) { + if ( ! Helper::docstringContainsHint(docstring, currentHint, &arguments) ) { continue; } // If the hint word appears in the docstring, run the evaluation function. if ( knownDecoratorHints[currentHint](arguments, currentHint) ) { // We indeed found something, so we're done. - return; + return docstringType; } } // if none of the above decorator-finding methods worked, just use the ordinary return type. - return encounter(type, DeclarationPointer(useDeclaration)); + return docstringType; } void ExpressionVisitor::visitSubscript(SubscriptAst* node) diff --git a/duchain/helpers.h b/duchain/helpers.h --- a/duchain/helpers.h +++ b/duchain/helpers.h @@ -159,9 +159,13 @@ return 0; }; - static bool docstringContainsHint(Declaration* declaration, const QString& hintName, QStringList* args = 0) { + static bool docstringContainsHint(const Declaration* declaration, + const QString& hintName, QStringList* args = 0) { + return docstringContainsHint(declaration->comment(), hintName, args); + } + + static bool docstringContainsHint(const QString& comment, const QString& hintName, QStringList* args = 0) { // TODO cache types! this is horribly inefficient - const QString& comment = declaration->comment(); const QString search = "! " + hintName + " !"; int index = comment.indexOf(search); if ( index >= 0 ) { diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -794,6 +794,7 @@ visitor->searchingForType = expectedType; visitor->visitCode(m_ast.data()); QEXPECT_FAIL("lambda", "not implemented: aliasing lambdas", Continue); + QEXPECT_FAIL("init_class_nodecl", "aliasing info lost", Continue); QCOMPARE(visitor->found, true); } @@ -942,11 +943,24 @@ QTest::newRow("tuple_unsure") << "q = (3, str())\nq=(str(), 3)\ncheckme, _ = q" << "unsure (int, str)"; + QTest::newRow("init_class") << "class Foo:\n" + " def __init__(self): pass\n" + " def __call__(self): return 1.5\n" + "checkme = Foo()\n" << "Foo"; + QTest::newRow("init_class_nodecl") << "class Foo:\n" + " def __init__(self): pass\n" + " def __call__(self): return 1.5\n" + "a = [Foo]\n" + "checkme = a[0]()\n" << "Foo"; QTest::newRow("call_class") << "class Foo:\n" " def __call__(self):\n" " return 0\n" "f = Foo()\n" "checkme = f()\n" << "int"; + QTest::newRow("call_class_nodecl") << "class Foo:\n" + " def __call__(self): return 1.5\n" + "a = [Foo()]\n" + "checkme = a[0]()" << "float"; QTest::newRow("classmethod") << "class Foo:\n" " @classmethod\n" " def foo(cls):\n"