diff --git a/duchain/declarationbuilder.h b/duchain/declarationbuilder.h --- a/duchain/declarationbuilder.h +++ b/duchain/declarationbuilder.h @@ -153,6 +153,9 @@ bool isAlias; }; + /** @brief If sourceType is a container that can be unpacked into outTypes, do so. */ + void tryUnpackType(AbstractType::Ptr sourceType, QVector& outTypes, int starred); + /** * @brief Handle a variable assignment to @p name and give it the type @p element. */ diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -388,7 +388,7 @@ if ( node->iterator ) { ExpressionVisitor v(currentContext()); v.visitNode(node->iterator); - assignToUnknown(node->target, Helper::contentOfIterable(v.lastType())); + assignToUnknown(node->target, Helper::contentOfIterable(v.lastType(), topContext())); } Python::ContextBuilder::visitFor(node); } @@ -497,7 +497,7 @@ ExpressionVisitor v(currentContext()); v.visitNode(node->iterator); - assignToUnknown(node->target, Helper::contentOfIterable(v.lastType())); + assignToUnknown(node->target, Helper::contentOfIterable(v.lastType(), topContext())); } void DeclarationBuilder::visitImport(ImportAst* node) @@ -1218,9 +1218,7 @@ } } -void tryUnpackType(AbstractType::Ptr sourceType, QVector& outTypes, int starred) { - // Helper for assignToTuple() below. - // If sourceType is a container that can be unpacked into outTypes, do so. +void DeclarationBuilder::tryUnpackType(AbstractType::Ptr sourceType, QVector& outTypes, int starred) { if ( const auto indexed = sourceType.cast() ) { int spare = indexed->typesCount() - outTypes.length(); if ( spare < -1 or (starred == -1 and spare != 0) ) { @@ -1239,7 +1237,7 @@ } } } else { - auto content = Helper::contentOfIterable(sourceType); + auto content = Helper::contentOfIterable(sourceType, topContext()); if ( !Helper::isUsefulType(content) ) { return; } diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -264,7 +264,7 @@ DUChainWriteLocker lock; auto intType = typeObjectForIntegralType("int"); auto enumerated = enumeratedTypeVisitor.lastType(); - auto result = listOfTuples(intType, Helper::contentOfIterable(enumerated)); + auto result = listOfTuples(intType, Helper::contentOfIterable(enumerated, topContext())); encounter(result, DeclarationPointer(useDeclaration)); return true; }; diff --git a/duchain/helpers.h b/duchain/helpers.h --- a/duchain/helpers.h +++ b/duchain/helpers.h @@ -99,7 +99,7 @@ * @param iterable Type to get the contents of. Can be an unsure. * @return KDevelop::AbstractType::Ptr Content type. Might be an unsure. */ - static AbstractType::Ptr contentOfIterable(const AbstractType::Ptr iterable); + static AbstractType::Ptr contentOfIterable(const AbstractType::Ptr iterable, const TopDUContext* topContext); /** * @brief Get a list of types inside the passed type which match the specified filter. diff --git a/duchain/helpers.cpp b/duchain/helpers.cpp --- a/duchain/helpers.cpp +++ b/duchain/helpers.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -488,35 +489,40 @@ return TypeUtils::isUsefulType(type); } -AbstractType::Ptr Helper::contentOfIterable(const AbstractType::Ptr iterable) +AbstractType::Ptr Helper::contentOfIterable(const AbstractType::Ptr iterable, const TopDUContext* topContext) { - auto items = filterType(iterable, - [](AbstractType::Ptr t) { - return ListType::Ptr::dynamicCast(t) || IndexedContainer::Ptr::dynamicCast(t); - }, - [](AbstractType::Ptr t) { - if (auto map = MapType::Ptr::dynamicCast(t)) { - // Iterating over dicts gets keys, not values - return map->keyType().abstractType(); - } - else if ( auto variable = ListType::Ptr::dynamicCast(t) ) { - return AbstractType::Ptr(variable->contentType().abstractType()); - } - else { - auto indexed = t.cast(); - return indexed->asUnsureType(); + auto types = filterType(iterable, + [](AbstractType::Ptr t) { return t->whichType() == AbstractType::TypeStructure; } ); + + static const IndexedIdentifier iterId(KDevelop::Identifier("__iter__")); + static const IndexedIdentifier nextId(KDevelop::Identifier("__next__")); + AbstractType::Ptr content(new IntegralType(IntegralType::TypeMixed)); + + for ( const auto& type: types ) { + if ( auto map = type.cast() ) { + // Iterating over dicts gets keys, not values + content = mergeTypes(content, map->keyType().abstractType()); + continue; + } + else if ( auto list = type.cast() ) { + content = mergeTypes(content, list->contentType().abstractType()); + continue; + } + else if ( auto indexed = type.cast() ) { + content = mergeTypes(content, indexed->asUnsureType()); + continue; + } + DUChainReadLocker lock; + // Content of an iterable object is iterable.__iter__().__next__(). + if ( auto iterFunc = dynamic_cast(accessAttribute(type, iterId, topContext)) ) { + if ( auto iterator = iterFunc->type()->returnType().cast() ) { + if ( auto nextFunc = dynamic_cast(accessAttribute(iterator, nextId, topContext)) ) { + content = mergeTypes(content, nextFunc->type()->returnType()); + } } } - ); - - if ( items.size() == 1 ) { - return items.first(); - } - auto unsure = AbstractType::Ptr(new UnsureType); - for ( auto type: items ) { - Helper::mergeTypes(unsure, type); } - return unsure; + return content; } AbstractType::Ptr Helper::mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr newType) 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("return_builtin_iterator", "fake builtin iter()", Continue); QCOMPARE(visitor->found, true); } @@ -942,6 +943,21 @@ QTest::newRow("tuple_unsure") << "q = (3, str())\nq=(str(), 3)\ncheckme, _ = q" << "unsure (int, str)"; + QTest::newRow("custom_iterable") << "class Gen2:\n" + " def __iter__(self): return self\n" + " def __next__(self): return 'blah'\n" + "for checkme in Gen2(): pass" << "str"; + QTest::newRow("separate_iterator") << "class Foo:\n" + " def __iter__(self): return Bar()\n" + " def __next__(self): return 'blah'\n" // Not used (or shouldn't be!) + "class Bar:\n" + " def __next__(self): return {1}\n" + "checkme = [a for a in Foo()]" << "list of set of int"; + QTest::newRow("return_builtin_iterator") << "class Gen2:\n" + " contents = [1, 2, 3]\n" + " def __iter__(self): return iter(Gen2.contents)\n" + "for checkme in Gen2(): pass" << "int"; + QTest::newRow("call_class") << "class Foo:\n" " def __call__(self):\n" " return 0\n" @@ -1459,6 +1475,11 @@ QTest::newRow("unpack_from_list_inplace") << "a, b = [1, 2, 3]" << QStringList{"a", "b"} << QStringList{"int", "int"}; QTest::newRow("unpack_from_list_indirect") << "c = [1, 2, 3]\na, b = c" << QStringList{"a", "b"} << QStringList{"int", "int"}; + QTest::newRow("unpack_custom_iterable") << + "class Foo:\n" + " def __iter__(self): return self\n" + " def __next__(self): return 1.5\n" + "a, *b = Foo()" << QStringList{"a", "b"} << QStringList {"float", "list of float"}; QTest::newRow("for_loop_simple") << "for i in range(3): pass" << QStringList{"i"} << QStringList{"int"}; QTest::newRow("for_loop_unpack") << "for a, b in [(3, 5.1)]: pass" << QStringList{"a", "b"} << QStringList{"int", "float"};