diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -366,14 +366,21 @@ void ExpressionVisitor::visitSubscript(SubscriptAst* node) { AstDefaultVisitor::visitNode(node->value); - if ( node->slice && node->slice->astType == Ast::IndexAstType ) { - DUChainReadLocker lock; - auto indexedTypes = Helper::filterType(lastType(), [](AbstractType::Ptr toFilter) { - return toFilter.cast(); - }); - for ( IndexedContainer::Ptr indexed: indexedTypes ) { - // TODO This loop currently only uses the first result, it could construct - // an unsure from all the matches. + + auto valueTypes = Helper::filterType(lastType(), [](AbstractType::Ptr) { return true; }); + AbstractType::Ptr result(new IntegralType(IntegralType::TypeMixed)); + + foreach (const auto& type, valueTypes) { + if ( (node->slice && node->slice->astType != Ast::IndexAstType) && + (type.cast() || type.cast()) ) { + if ( type.cast() ) { + continue; // Can't slice dicts. + } + // Assume that slicing (e.g. foo[3:5]) a tuple/list returns the same type. + // TODO: we could do better for some tuple slices. + result = Helper::mergeTypes(result, type); + } + else if ( const auto& indexed = type.cast() ) { IndexAst* sliceIndexAst = static_cast(node->slice); NumberAst* number = nullptr; bool invert = false; @@ -394,43 +401,27 @@ sliceIndex += indexed->typesCount(); } if ( sliceIndex < indexed->typesCount() && sliceIndex >= 0 ) { - return encounter(indexed->typeAt(sliceIndex).abstractType()); + result = Helper::mergeTypes(result, indexed->typeAt(sliceIndex).abstractType()); + continue; } } - // the exact index is unknown, use unsure - return encounter(indexed->asUnsureType().cast()); - } - auto variableTypes = Helper::filterType(lastType(), [](AbstractType::Ptr toFilter) { - return toFilter.cast(); - }); - if ( ! variableTypes.isEmpty() ) { - AbstractType::Ptr result(new IntegralType(IntegralType::TypeMixed)); - for ( auto variable: variableTypes ) { - result = Helper::mergeTypes(result, variable->contentType().abstractType()); + result = Helper::mergeTypes(result, indexed->asUnsureType()); + } + else if ( const auto& listType = type.cast() ) { + result = Helper::mergeTypes(result, listType->contentType().abstractType()); + } + else { + // Type wasn't one with custom handling, so use return type of __getitem__(). + DUChainReadLocker lock; + Declaration* function = Helper::accessAttribute(type, "__getitem__", context()); + if ( function && function->isFunctionDeclaration() ) { + if ( FunctionType::Ptr functionType = function->type() ) { + result = Helper::mergeTypes(result, functionType->returnType()); + } } - return encounter(result); - } - } - - // If that does not work and we have a slice like [3:5], guess it will remain the same type. - // That is an approximation we have to make now, should optimally be corrected later. - // The reason is that we'd need to parse decorators from the __getitem__ method to support - // list/dict/etc properly otherwise, which requires a bit of refactoring first. TODO do this - if ( node->slice && node->slice->astType != Ast::IndexAstType ) { - return; - } - - // Otherwise, try to use __getitem__. - DUChainReadLocker lock; - Declaration* function = Helper::accessAttribute(lastType(), "__getitem__", context()); - if ( function && function->isFunctionDeclaration() ) { - if ( FunctionType::Ptr functionType = function->type() ) { - return encounter(functionType->returnType()); } } - - // Otherwise, give up - return encounterUnknown(); + encounter(result); } void ExpressionVisitor::visitList(ListAst* node) diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -1393,6 +1393,12 @@ "class Foo:\n def __getitem__(self, key):\n return str()\n" "def bar():\n return Foo()\n" "checkme = bar()[0]" << "str" << true; + QTest::newRow("subscript_unknown_index") << "a = 1,str()\ncheckme = a[5-4]" << "unsure (int, str)" << true; + QTest::newRow("subscript_unsure") << "a = 1,2\na=[str()]\ncheckme = a[0]" << "unsure (int, str)" << true; + QTest::newRow("subscript_unsure_getitem") << + "class Foo:\n def __getitem__(self, key):\n return str()\n" + "class Bar:\n def __getitem__(self, key):\n return float()\n" + "a = Foo()\na=Bar()\na=[1,2]\ncheckme = a[1]" << "unsure (str, float, int)" << true; } void PyDUChainTest::testVariableCreation()