diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -533,13 +533,19 @@ ExpressionVisitor contentVisitor(this); ExpressionVisitor keyVisitor(this); if ( type ) { - foreach ( ExpressionAst* content, node->values ) { - contentVisitor.visitNode(content); - type->addContentType(contentVisitor.lastType()); - } - foreach ( ExpressionAst* key, node->keys ) { - keyVisitor.visitNode(key); - type->addKeyType(keyVisitor.lastType()); + Q_ASSERT(node->keys.length() == node->values.length()); + for ( int ii = 0; ii < node->values.length(); ++ii ) { + contentVisitor.visitNode(node->values.at(ii)); + if ( node->keys.at(ii) ) { + type->addContentType(contentVisitor.lastType()); + keyVisitor.visitNode(node->keys.at(ii)); + type->addKeyType(keyVisitor.lastType()); + } + else if ( auto unpackedType = contentVisitor.lastType().cast() ) { + // Key is null for `{**foo}` + type->addContentType(unpackedType->contentType().abstractType()); + type->addKeyType(unpackedType->keyType().abstractType()); + } } } encounter(AbstractType::Ptr::staticCast(type)); diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -1483,6 +1483,9 @@ QTest::newRow("set_of_int_call") << "checkme = set({1, 2, 3})" << "int" << false; QTest::newRow("set_generator") << "checkme = {i for i in [1, 2, 3]}" << "int" << false; QTest::newRow("dict_of_int") << "checkme = {a:1, b:2, c:3}" << "int" << false; + QTest::newRow("dict_from_unpacked") << "checkme = {**{'a': 1}}" << "dict of str : int" << true; + QTest::newRow("dict_from_varied") << "checkme = {**{'a': 1}, 1: 1.5}" << + "dict of unsure (str, int) : unsure (int, float)" << true; QTest::newRow("dict_of_int_call") << "checkme = dict({a:1, b:2, c:3})" << "int" << false; QTest::newRow("dict_generator") << "checkme = {\"Foo\":i for i in [1, 2, 3]}" << "int" << false; QTest::newRow("dict_access") << "list = {a:1, b:2, c:3}\ncheckme = list[0]" << "int" << true; diff --git a/parser/ast.h b/parser/ast.h --- a/parser/ast.h +++ b/parser/ast.h @@ -536,7 +536,7 @@ class KDEVPYTHONPARSER_EXPORT DictAst : public ExpressionAst { public: DictAst(Ast* parent); - QList keys; + QList keys; // WARNING: Can contain null elements: `{**other}` QList values; }; diff --git a/parser/conversionGenerator.py b/parser/conversionGenerator.py --- a/parser/conversionGenerator.py +++ b/parser/conversionGenerator.py @@ -256,11 +256,6 @@ if ( ! node ) return nodelist; for ( int i=0; i < node->size; i++ ) { T* currentNode = static_cast(node->elements[i]); - if ( ! currentNode ) { - // can happen in some obscure cases, e.g. a = {**{3:5 for x in range(3)}} (dict->key is null) - qWarning() << "warning: null child node on" << node; - continue; - } Ast* result = visitNode(currentNode); K* transformedNode = static_cast(result); nodelist.append(transformedNode); diff --git a/parser/generated.h b/parser/generated.h --- a/parser/generated.h +++ b/parser/generated.h @@ -39,11 +39,6 @@ if ( ! node ) return nodelist; for ( int i=0; i < node->size; i++ ) { T* currentNode = static_cast(node->elements[i]); - if ( ! currentNode ) { - // can happen in some obscure cases, e.g. a = {**{3:5 for x in range(3)}} (dict->key is null) - qWarning() << "warning: null child node on" << node; - continue; - } Ast* result = visitNode(currentNode); K* transformedNode = static_cast(result); nodelist.append(transformedNode);