diff --git a/duchain/declarationbuilder.h b/duchain/declarationbuilder.h --- a/duchain/declarationbuilder.h +++ b/duchain/declarationbuilder.h @@ -169,7 +169,7 @@ /** * @brief Attempts to determine the unpacked type(s) of the right-hand side of an assignment. * - * @param items The expression on the right-hand side of the assignment + * @param source The source type on the right-hand side of the assignment * @param fillWhenLengthMissing If non-zero and the object in @p items has an indefinite length but * a definite content type, return this many copies of that type * @@ -177,20 +177,7 @@ * a, b = "Foo", 3.5 (@p fillWhenLengthMissing ignored here because the right-hand side * has a definite amount of items) -> returns str, float */ - QList sourcesOfAssignment(ExpressionAst* items, int fillWhenLengthMissing=-1) const; - - /** - * @brief Given a list of targets (@see targetsOfAssignment) and sources (@see sourcesOfAssignment) and a - * @p index, matches the sources with the targets and returns the type which belongs to @p index. - * - * @param targets Left-hand side of the assignment - * @param sources Right-hand side of the assignment - * @param index index of the item to return the type for - * @param rhs Pass the right-hand side of the assignment again so that its compound type can be used in case - * it consists of more than one element and unpacking is not possible; example: a = 1, 2 - */ - SourceType selectSource(const QList& targets, const QList& sources, - int index, ExpressionAst* rhs) const; + QVector unpackAssignmentSource(const SourceType& source, int fillWhenLengthMissing) const; /** * @brief Handle a variable assignment to @p name and give it the type @p element. @@ -208,6 +195,16 @@ void assignToAttribute(AttributeAst* attribute, const SourceType& element); /** + * @brief Handle assignment to a target @p target with rhs type @p element. + */ + void assignToTuple(TupleAst* tuple, const SourceType& element); + + /** + * @brief Handle assignment to a target @p target with rhs type @p element. + */ + void assignToUnknown(ExpressionAst* target, const SourceType& element); + + /** * @brief Find all existing declarations for the identifier @p node */ QList existingDeclarationsForNode(Identifier* node); diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -1198,92 +1198,30 @@ return lhsExpressions; } -QList DeclarationBuilder::sourcesOfAssignment(ExpressionAst* items, - int fillWhenLengthMissing) const +QVector + DeclarationBuilder::unpackAssignmentSource(const SourceType& source, + int fillWhenLengthMissing) const { - QList sources; - QList values; - - // TODO rework this function. It doesn't make much sense like this and could work much better. - if ( items && items->astType == Ast::TupleAstType ) { - values = static_cast(items)->elements; - } - else { - // This handles the a, b, c = [1, 2, 3] case. Since the assignment can also be like - // d = [1, 2, 3]; a, b, c = d we can't generally know the amount of elements - // in the right operand; so all elements are treated to have the same type. - if ( fillWhenLengthMissing > 0 ) { - ExpressionVisitor v(currentContext()); - v.visitNode(items); - auto container = ListType::Ptr::dynamicCast(v.lastType()); - if ( container ) { - AbstractType::Ptr content = container->contentType().abstractType(); - for ( ; fillWhenLengthMissing != 0; fillWhenLengthMissing-- ) { - sources << SourceType{ content, KDevelop::DeclarationPointer(), false }; - } - return sources; - } + QVector sources; + if ( const IndexedContainer::Ptr container = source.type.cast() ) { + // RHS is a tuple or similar, unpack that. + for (int i = 0; i < container->typesCount(); ++i) { + sources << SourceType{ + container->typeAt(i).abstractType(), + DeclarationPointer(), + false + }; } - - // Otherwise, proceed normally. - values << items; - } - - foreach ( ExpressionAst* value, values ) { - ExpressionVisitor v(currentContext()); - v.visitNode(value); - - sources << SourceType{ - v.lastType(), - DeclarationPointer(Helper::resolveAliasDeclaration(v.lastDeclaration().data())), - v.isAlias() - }; } - return sources; -} - -DeclarationBuilder::SourceType DeclarationBuilder::selectSource(const QList& targets, - const QList& sources, - int index, ExpressionAst* rhs) const -{ - bool canUnpack = targets.length() == sources.length(); - SourceType element; - // If the length of the right and the left side matches, exact unpacking can be done. - // example code: a, b, c = 3, 4, 5 - // If the left side only contains one entry, unpacking never happens, and the left side - // is instead assigned a container type if applicable - // example code: a = 3, 4, 5 - if ( canUnpack ) { - element = sources.at(index); - } - else if ( targets.length() == 1 ) { - ExpressionVisitor v(currentContext()); - v.visitNode(rhs); - element = SourceType{ - v.lastType(), - DeclarationPointer(Helper::resolveAliasDeclaration(v.lastDeclaration().data())), - v.isAlias() - }; - } - else if ( ! sources.isEmpty() ) { - // the assignment is of the form "foo, bar, ... = ..." (tuple unpacking) - // this one is for the case that the tuple unpacking is not written down explicitly, for example - // a = (1, 2, 3); b, c, d = a - // the other case (b, c, d = 1, 2, 3) is handled above. - if ( const IndexedContainer::Ptr container = sources.first().type.cast() ) { - if ( container->typesCount() == targets.length() ) { - element.type = container->typeAt(index).abstractType(); - element.isAlias = false; - } + else if ( auto container = ListType::Ptr::dynamicCast(source.type) ) { + // RHS is a list or similar, can't tell contents apart. + // Return content type * requested length. + AbstractType::Ptr content = container->contentType().abstractType(); + for ( ; fillWhenLengthMissing != 0; fillWhenLengthMissing-- ) { + sources << SourceType{ content, DeclarationPointer(), false }; } } - if ( ! element.type ) { - // use mixed if none of the previous ways of determining the type worked. - element.type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); - element.declaration = nullptr; - element.isAlias = false; - } - return element; + return sources; } void DeclarationBuilder::assignToName(NameAst* target, const DeclarationBuilder::SourceType& element) @@ -1417,34 +1355,78 @@ } } +void DeclarationBuilder::assignToTuple(TupleAst* tuple, const DeclarationBuilder::SourceType& element) { + auto sources = unpackAssignmentSource(element, tuple->elements.length()); + int ii = 0; + bool foundStarred = false; + foreach ( ExpressionAst* target, tuple->elements ) { + // Fallback, if we ran out of known types. + auto source = SourceType{ + AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)), + DeclarationPointer(), + false + }; + + if ( target->astType == Ast::StarredAstType ) { + // PEP-3132. `a, *b, c = 1, 2, 3, 4 -> b = [2, 3]` + // Starred expression is assigned a list of the values not used by unstarred ones. + DUChainReadLocker lock; + auto type = ExpressionVisitor::typeObjectForIntegralType("list"); + lock.unlock(); + if ( !foundStarred ) { // Only allowed once, return unknown list. + // Count sources not used by other targets, slurp that many into list. + int leftovers = sources.length() - tuple->elements.length() + 1; + for ( ; leftovers > 0; leftovers--, ii++ ) { + type->addContentType(sources.at(ii).type); + } + } + source.type = AbstractType::Ptr::staticCast(type); + // List is assigned to the child expression. + target = static_cast(target)->value; + foundStarred = true; + } + else if ( ii < sources.length() ) { + source = sources.at(ii); + ii++; + } + assignToUnknown(target, source); + } +} + +void DeclarationBuilder::assignToUnknown(ExpressionAst* target, const DeclarationBuilder::SourceType& element) { + // Must be a nicer way to do this. + if ( target->astType == Ast::TupleAstType ) { + // Assignments of the form "a, b = 1, 2" or "a, b = c" + assignToTuple(static_cast(target), element); + } + else if ( target->astType == Ast::NameAstType ) { + // Assignments of the form "a = 3" + assignToName(static_cast(target), element); + } + else if ( target->astType == Ast::SubscriptAstType ) { + // Assignments of the form "a[0] = 3" + assignToSubscript(static_cast(target), element); + } + else if ( target->astType == Ast::AttributeAstType ) { + // Assignments of the form "a.b = 3" + assignToAttribute(static_cast(target), element); + } +} + void DeclarationBuilder::visitAssignment(AssignmentAst* node) { AstDefaultVisitor::visitAssignment(node); - // Because of tuple unpacking, it is required to gather the left- and right hand side - // expressions / types first, then match them together in a second step. - const QList& targets = targetsOfAssignment(node->targets); - const QList& sources = sourcesOfAssignment(node->value, targets.size() > 1 ? targets.size() : -1); - // Now all the information about left- and right hand side entries is ready, - // and creation / updating of variables can start. - int i = 0; - foreach ( ExpressionAst* target, targets ) { - SourceType element(selectSource(targets, sources, i, node->value)); + ExpressionVisitor v(currentContext()); + v.visitNode(node->value); + auto sourceType = SourceType{ + v.lastType(), + DeclarationPointer(Helper::resolveAliasDeclaration(v.lastDeclaration().data())), + v.isAlias() + }; - // Handling the tuple unpacking stuff is done now, and we can proceed as if there was no tuple unpacking involved. - if ( target->astType == Ast::NameAstType ) { - // Assignments of the form "a = 3" - assignToName(static_cast(target), element); - } - else if ( target->astType == Ast::SubscriptAstType ) { - // Assignments of the form "a[0] = 3" - assignToSubscript(static_cast(target), element); - } - else if ( target->astType == Ast::AttributeAstType ) { - // Assignments of the form "a.b = 3" - assignToAttribute(static_cast(target), element); - } - i += 1; + foreach(ExpressionAst* target, node->targets) { + assignToUnknown(target, sourceType); } } diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -861,6 +861,31 @@ QTest::newRow("tuple_simple2") << "mytuple = 3, 5.5\nfoobar, checkme = mytuple" << "float"; QTest::newRow("tuple_simple3") << "mytuple = 3, 5.5, \"str\", 3, \"str\"\na, b, c, d, checkme = mytuple" << "str"; + QTest::newRow("tuple_single") << "checkme = 4," << "tuple"; + QTest::newRow("tuple_single2") << "checkme, = 4," << "int"; + QTest::newRow("tuple_single3") << "mytuple = 4,\ncheckme, = mytuple" << "int"; + + QTest::newRow("tuple_ext_unpack") << "mytuple = 3, 5.5\nfoobar, *starred, checkme = mytuple" << "float"; + QTest::newRow("tuple_ext_unpack2") << "mytuple = 3, 5.5\nfoobar, *checkme, another = mytuple" << "list"; + QTest::newRow("tuple_ext_unpack3") << "mytuple = 3, 5.5\nfoobar, *checkme = mytuple" << "list of float"; + QTest::newRow("tuple_ext_unpack4") << "mytuple = 3, 5.5\n*checkme, = mytuple" << "list of unsure (int, float)"; + + QTest::newRow("tuple_nested") << "mytuple = 3, ('foo', 5.5)\ncheckme, foobar = mytuple" << "int"; + QTest::newRow("tuple_nested2") << "mytuple = 3, ('foo', 5.5)\nfoobar, (checkme, other) = mytuple" << "str"; + QTest::newRow("tuple_nested3") << "mytuple = ((7, 'foo'), 5.5), 3\n((baz, checkme), other), foo = mytuple" << "str"; + + // This isn't actually defined behaviour, but it works in CPython, so people use it... + QTest::newRow("tuple_nested_ext") << "mytuple = (2, ('foo', 'bar', 6), 7)\na, (b, *checkme, c), *d = mytuple" << "list of str"; + + QTest::newRow("tuple_multi_assign") << "mytuple = 2, 'foo'\ncheckme = a = mytuple" << "tuple"; + QTest::newRow("tuple_multi_assign2") << "mytuple = 2, 'foo'\ncheckme, a = b = mytuple" << "int"; + + QTest::newRow("list_unpack") << "mylist = [1, 2, 3]\ncheckme, b, c = mylist" << "int"; + QTest::newRow("list_unpack2") << "mylist = [1, 'x', 3]\ncheckme, b, c = mylist" << "unsure (int, str)"; + + QTest::newRow("list_ext_unpack") << "mylist = [1, 2, 3]\n*checkme, foo = mylist" << "list of int"; + QTest::newRow("list_ext_unpack2") << "mylist = [1, 'x', 3]\n*checkme, foo = mylist" << "list of unsure (int, str)"; + QTest::newRow("if_expr_sure") << "checkme = 3 if 7 > 9 else 5" << "int"; QTest::newRow("unary_op") << "checkme = -42" << "int";