diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -1059,32 +1059,40 @@ lock.unlock(); DUChainWriteLocker wlock; foreach ( KeywordAst* keyword, node->keywords ) { - if ( ! keyword->argumentName ) { - // 'keyword is actually an unpacked dict: `foo(**{'a': 12}). Not handled currently. - continue; - } wlock.unlock(); ExpressionVisitor argumentVisitor(currentContext()); argumentVisitor.visitNode(keyword->value); if ( ! argumentVisitor.lastType() ) { continue; } wlock.lock(); - HintedType::Ptr addType = HintedType::Ptr(new HintedType()); - openType(addType); - addType->setType(argumentVisitor.lastType()); - addType->setCreatedBy(topContext(), m_futureModificationRevision); - closeType(); bool matchedNamedParam = false; - for (int ip = currentParamIndex; ip < paramsAvailable; ++ip ) { - if ( parameters.at(ip)->identifier().toString() != keyword->argumentName->value ) { - continue; + HintedType::Ptr addType = HintedType::Ptr(new HintedType()); + if ( keyword->argumentName ) { + openType(addType); + addType->setType(argumentVisitor.lastType()); + addType->setCreatedBy(topContext(), m_futureModificationRevision); + closeType(); + for (int ip = currentParamIndex; ip < paramsAvailable; ++ip ) { + if ( parameters.at(ip)->identifier().toString() != keyword->argumentName->value ) { + continue; + } + matchedNamedParam = true; + auto newType = Helper::mergeTypes(parameters.at(ip)->abstractType(), addType); + functionType->removeArgument(ip); + functionType->addArgument(newType, ip); + parameters.at(ip)->setType(newType); } - matchedNamedParam = true; - auto newType = Helper::mergeTypes(parameters.at(ip)->abstractType(), addType); - functionType->removeArgument(ip); - functionType->addArgument(newType, ip); - parameters.at(ip)->setType(newType); + } + else if ( auto unpackedDict = argumentVisitor.lastType().cast() ) { + // 'keyword is actually an unpacked dict: `foo(**{'a': 12}). + openType(addType); + addType->setType(unpackedDict->contentType().abstractType()); + addType->setCreatedBy(topContext(), m_futureModificationRevision); + closeType(); + } + else { // Maybe the dict type wasn't loaded yet, or something else happened. + continue; } if ( ! matchedNamedParam && kwargsDict ) { kwargsDict->addContentType(addType); diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -943,8 +943,14 @@ QTest::newRow("no_hints_type") << "def myfun(arg): arg = 3; return arg\ncheckme = myfun(3)" << "int"; QTest::newRow("hints_type") << "def myfun(arg): return arg\ncheckme = myfun(3)" << "int"; QTest::newRow("args_type") << "def myfun(*args): return args[0]\ncheckme = myfun(3)" << "int"; - QTest::newRow("kwarg_type") << "def myfun(**args): return args['a']\ncheckme = myfun(a=3)" << "int"; + QTest::newRow("kwarg_type") << "def myfun(**kwargs): return kwargs['a']\ncheckme = myfun(a=3)" << "int"; + QTest::newRow("dict_kwarg_type") << "def foo(**kwargs): return kwargs['']\ncheckme = foo(**{'a': 12})" << "int"; + QTest::newRow("dict_norm_kwarg_type") << "def foo(**kwargs): return kwargs['']\n" + "checkme = foo(**{'a': 12}, b=1.2)" << "unsure (int, float)"; + QTest::newRow("multi_dict_kwarg_type") << "def foo(**kwargs): return kwargs['']\n" + "checkme = foo(**{'a': 12}, b=1.2, **{'c': ''})" << "unsure (int, float, str)"; QTest::newRow("named_arg_type") << "def myfun(arg): return arg\ncheckme = myfun(arg=3)" << "int"; + QTest::newRow("arg_args_type") << "def myfun(arg, *args): return args[0]\n" "checkme = myfun(3, str())" << "str"; QTest::newRow("arg_kwargs_type") << "def myfun(arg, **kwargs): return kwargs['a']\n"