diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -959,12 +959,15 @@ return; } QVector parameters = parameterContext->localDeclarations(); - const int specialParamsCount = (function->vararg() > 0) + (function->kwarg() > 0); + if ( parameters.isEmpty() ) { + return; + } + const int specialParamsCount = (function->vararg() != -1) + (function->kwarg() != -1); // Look for the "self" in the argument list, the type of that should not be updated. bool hasSelfParam = false; if ( ( function->context()->type() == DUContext::Class || funcInfo.isConstructor ) - && ! parameters.isEmpty() && ! function->isStatic() ) + && ! function->isStatic() ) { // ... unless for some reason the function only has *vararg, **kwarg as parameters // (this could happen for example if the method is static but kdev-python does not know, @@ -998,7 +1001,7 @@ // passed as an argument, and update the parameter accordingly. // Stop if more parameters supplied than possible, and we're not at the vararg. for ( ; ( atVararg || currentParamIndex < paramsAvailable ) && currentArgumentIndex < argsAvailable; - currentParamIndex++, currentArgumentIndex++ ) + currentArgumentIndex++ ) { atVararg = atVararg || currentParamIndex == function->vararg(); // Not >=, nonexistent vararg is -1. @@ -1044,19 +1047,20 @@ functionType->addArgument(newType, currentParamIndex); function->setAbstractType(functionType.cast()); parameters.at(currentParamIndex)->setType(newType); + currentParamIndex++; } } + // **kwargs is always the last parameter + MapType::Ptr kwargsDict; + if ( function->kwarg() != -1 ) { + kwargsDict = parameters.last()->abstractType().cast(); + } lock.unlock(); DUChainWriteLocker wlock; - if ( function->kwarg() < 0 || parameters.isEmpty() ) { - // no kwarg, stop here. - return; - } foreach ( KeywordAst* keyword, node->keywords ) { - AbstractType::Ptr param = parameters.last()->abstractType(); - auto list = param.cast(); - if ( ! list ) { + if ( ! keyword->argumentName ) { + // 'keyword is actually an unpacked dict: `foo(**{'a': 12}). Not handled currently. continue; } wlock.unlock(); @@ -1071,9 +1075,23 @@ addType->setType(argumentVisitor.lastType()); addType->setCreatedBy(topContext(), m_futureModificationRevision); closeType(); - list->addContentType(addType.cast()); - parameters.last()->setAbstractType(list.cast()); + bool matchedNamedParam = false; + 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); + } + if ( ! matchedNamedParam && kwargsDict ) { + kwargsDict->addContentType(addType); + parameters.last()->setAbstractType(kwargsDict); + } } + function->setAbstractType(functionType); } void DeclarationBuilder::visitCall(CallAst* node) diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -942,10 +942,17 @@ 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("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" "checkme = myfun(12, a=str())" << "str"; + QTest::newRow("named_kwargs_type_1") << "def myfun(arg, **kwargs): return arg\n" + "checkme = myfun(arg=12, a=str())" << "int"; + QTest::newRow("named_kwargs_type_2") << "def myfun(arg, **kwargs): return kwargs['a']\n" + "checkme = myfun(arg=12, a=str())" << "str"; + QTest::newRow("kwargs_named_type") << "def myfun(arg, **kwargs): return kwargs['a']\n" + "checkme = myfun(a=str(), arg=12)" << "str"; QTest::newRow("varied_args_type_1") << "def myfun(arg, *args, **kwargs): return arg\n" "checkme = myfun(1, 1.5, a=str())" << "int"; QTest::newRow("varied_args_type_2") << "def myfun(arg, *args, **kwargs): return args[0]\n"