diff --git a/duchain/expressionvisitor.h b/duchain/expressionvisitor.h --- a/duchain/expressionvisitor.h +++ b/duchain/expressionvisitor.h @@ -80,15 +80,14 @@ virtual void visitNameConstant(NameConstantAst* node); /** - * @brief Checks the decorators of the given function declaration. + * @brief Checks for magic docstrings that override a call's return type. * - * @param node The node to visit - * @param funcDecl The call's function declaration, if any - * @param classDecl The call's class declaration, if any - * @param isConstructor whether a constructor is being called + * @param node The node to visit. + * @param normalType The return type as determined without docstrings. + * @param docstring Docstring of the function. */ - 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,61 @@ 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(); - } - } + auto declaration = Helper::resolveAliasDeclaration(v.lastDeclaration().data()); + if ( ! v.isAlias() && v.lastType() ) { + if ( auto functionType = v.lastType().cast() ) { + encounter(functionType->returnType()); + return; + } + if ( auto classType = v.lastType().cast() ) { + declaration = classType->declaration(topContext()); } } - else { - actualDeclaration = v.lastDeclaration().data(); - } - - if ( unidentifiedFunctionType ) { - encounter(unidentifiedFunctionType->returnType()); + 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 +200,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 +228,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 +245,7 @@ DUChainWriteLocker lock; auto intType = typeObjectForIntegralType("int"); auto enumerated = enumeratedTypeVisitor.lastType(); - auto result = listOfTuples(intType, Helper::contentOfIterable(enumerated, topContext())); - encounter(result, DeclarationPointer(useDeclaration)); + docstringType = listOfTuples(intType, Helper::contentOfIterable(enumerated, topContext())); return true; }; @@ -277,8 +260,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 +279,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,29 +290,26 @@ 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; }; - auto docstring = funcDecl->comment(); - if ( ! docstring.isEmpty() ) { - foreach ( const QString& currentHint, knownDecoratorHints.keys() ) { - QStringList 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; - } + foreach ( const QString& currentHint, knownDecoratorHints.keys() ) { + QStringList 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 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/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -795,6 +795,7 @@ visitor->visitCode(m_ast.data()); QEXPECT_FAIL("lambda", "not implemented: aliasing lambdas", Continue); QEXPECT_FAIL("return_builtin_iterator", "fake builtin iter()", Continue); + QEXPECT_FAIL("init_class_no_decl", "aliasing info lost", Continue); QCOMPARE(visitor->found, true); } @@ -963,11 +964,24 @@ " def __iter__(self): return iter(Gen2.contents)\n" "for checkme in Gen2(): pass" << "int"; + 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_no_decl") << "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_no_decl") << "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"