diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -953,7 +953,7 @@ const int specialParamsCount = (lastFunctionDeclaration->vararg() > 0) + (lastFunctionDeclaration->kwarg() > 0); // Look for the "self" in the argument list, the type of that should not be updated. - bool hasSelfArgument = false; + bool hasSelfParam = false; if ( ( lastFunctionDeclaration->context()->type() == DUContext::Class || funcInfo.isConstructor ) && ! parameters.isEmpty() && ! lastFunctionDeclaration->isStatic() ) { @@ -961,18 +961,30 @@ // (this could happen for example if the method is static but kdev-python does not know, // or if the user just made a mistake in his code) if ( specialParamsCount < parameters.size() ) { - hasSelfArgument = true; + hasSelfParam = true; } } - int currentParamIndex = hasSelfArgument; - int currentArgumentIndex = 0; + + lock.unlock(); + + bool explicitSelfArgument = false; + if ( hasSelfParam && ! lastFunctionDeclaration->isClassMethod() && node->function->astType == Ast::AttributeAstType ) { + // Calling an attribute, e.g. `instance.foo(arg)` or `MyClass.foo(instance, arg)`. + ExpressionVisitor valueVisitor(currentContext()); + valueVisitor.visitNode(static_cast(node->function)->value); + if ( valueVisitor.lastDeclaration().dynamicCast() && valueVisitor.isAlias() ) { + // Function is attribute of a class _type_ (not instance), so first arg is used as `self`. + explicitSelfArgument = true; + } + } + + int currentParamIndex = hasSelfParam; + int currentArgumentIndex = explicitSelfArgument; int indexInVararg = -1; int paramsAvailable = qMin(functiontype->arguments().length(), parameters.size()); int argsAvailable = node->arguments.size(); bool atVararg = false; - lock.unlock(); - // Iterate over all the arguments, trying to guess the type of the object being // passed as an argument, and update the parameter accordingly. // Stop if more parameters supplied than possible, and we're not at the vararg. @@ -1002,7 +1014,7 @@ DUChainWriteLocker wlock; if ( atVararg ) { indexInVararg++; - Declaration* parameter = parameters.at(lastFunctionDeclaration->vararg()+hasSelfArgument); + Declaration* parameter = parameters.at(lastFunctionDeclaration->vararg()+hasSelfParam); IndexedContainer::Ptr varargContainer = parameter->type(); qCDebug(KDEV_PYTHON_DUCHAIN) << "adding" << addType->toString() << "at position" << indexInVararg; if ( ! varargContainer ) continue; @@ -1021,8 +1033,8 @@ AbstractType::Ptr newType = Helper::mergeTypes(parameters.at(currentParamIndex)->abstractType(), addType.cast()); // TODO this does not correctly update the types in quickopen! Investigate why. - functiontype->removeArgument(currentArgumentIndex + hasSelfArgument); - functiontype->addArgument(newType, currentArgumentIndex + hasSelfArgument); + functiontype->removeArgument(currentParamIndex); + functiontype->addArgument(newType, currentParamIndex); lastFunctionDeclaration->setAbstractType(functiontype.cast()); parameters.at(currentParamIndex)->setType(newType); } diff --git a/duchain/tests/pyduchaintest.cpp b/duchain/tests/pyduchaintest.cpp --- a/duchain/tests/pyduchaintest.cpp +++ b/duchain/tests/pyduchaintest.cpp @@ -418,6 +418,12 @@ QTest::newRow("list_extend_missing") << "foo = []\nfoo.extend(missing)"; QTest::newRow("list_extend_missing_arg") << "foo = []\nfoo.extend()"; + QTest::newRow("method_of_call_with_list_arg") << + "class MyClass:\n" + " def bar(self): pass\n" + "def foo(x):\n" + " return MyClass()\n" + "foo([0]).bar()"; } void PyDUChainTest::testClassVariables() @@ -795,6 +801,7 @@ visitor->visitCode(m_ast.data()); QEXPECT_FAIL("lambda", "not implemented: aliasing lambdas", Continue); QEXPECT_FAIL("return_builtin_iterator", "fake builtin iter()", Continue); + QEXPECT_FAIL("parent_constructor_arg_type", "Not enough passes?", Continue); QEXPECT_FAIL("init_class_no_decl", "aliasing info lost", Continue); QCOMPARE(visitor->found, true); } @@ -964,6 +971,28 @@ " @staticmethod\n" " def method(arg): return arg\n" "checkme = MyClass().method(12)" << "int"; + QTest::newRow("method_explicit_self") << "class MyClass:\n" + " def method(self, arg): return arg\n" + "instance = MyClass()\n" + "checkme = MyClass.method(instance, 12)" << "int"; + QTest::newRow("clsmethod_explicit_self") << "class MyClass:\n" + " @classmethod\n" + " def method(cls, arg1, arg2): return arg2\n" + "instance = MyClass()\n" + "checkme = MyClass.method('a', 12)" << "int"; + QTest::newRow("staticmethod_explicit_self") << "class MyClass:\n" + " @staticmethod\n" + " def method(arg1, arg2): return arg1\n" + "instance = MyClass()\n" + "checkme = MyClass.method('a', 12)" << "str"; + QTest::newRow("parent_constructor_arg_type") << "class Base:\n" // https://bugs.kde.org/show_bug.cgi?id=369364 + " def __init__(self, foo):\n" + " self.foo = foo\n" + "class Derived(Base):\n" + " def __init__(self, foo):\n" + " Base.__init__(self, foo)\n" + "instance = Derived('string')\n" + "checkme = instance.foo" << "str"; QTest::newRow("tuple_unsure") << "q = (3, str())\nq=(str(), 3)\ncheckme, _ = q" << "unsure (int, str)";