diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index 54c6668bea6d..59065011b351 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -118,7 +118,7 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, PyDocStrin * @param name what to look for * @param inherited true: search in superclasses; false: only look for methods defined in this class * @param context context to be used to resolve ancestors - * @return method with given name or null. + * @return method with given name or null, prefers implementation over same name overloads. */ @Nullable PyFunction findMethodByName(@Nullable @NonNls final String name, boolean inherited, TypeEvalContext context); @@ -142,7 +142,8 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, PyDocStrin * * @param inherited true: search in superclasses, too. * @param context context to be used to resolve ancestors and check if this class is a new-style class - * @return a method that would be called first when an instance of this class is instantiated. + * @return a method that would be called first when an instance of this class is instantiated, + * prefers implementation over same name overloads. */ @Nullable PyFunction findInitOrNew(boolean inherited, @Nullable TypeEvalContext context); diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 5dcdc891feab..7a5f643560b9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -36,6 +36,7 @@ import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.types.*; +import com.jetbrains.python.pyi.PyiUtil; import com.jetbrains.python.toolbox.Maybe; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; @@ -504,12 +505,15 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla } private static class NameFinder implements Processor { + @NotNull + private final TypeEvalContext myContext; private T myResult; private final String[] myNames; private int myLastResultIndex = -1; private PyClass myLastVisitedClass = null; - NameFinder(String... names) { + NameFinder(@NotNull TypeEvalContext context, String... names) { + myContext = context; myNames = names; myResult = null; } @@ -535,11 +539,16 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla final int index = ArrayUtil.indexOf(myNames, target.getName()); // Do not depend on the order in which elements appear, always try to find the first one - if (index >= 0 && (myLastResultIndex == -1 || index < myLastResultIndex)) { - myLastResultIndex = index; - myResult = target; - if (index == 0) { - return false; + if (index >= 0) { + if (myLastResultIndex == -1 || + index < myLastResultIndex || + index == myLastResultIndex && PyiUtil.isOverload(myResult, myContext) && !PyiUtil.isOverload(target, myContext)) { + myLastResultIndex = index; + myResult = target; + + if (index == 0 && !PyiUtil.isOverload(myResult, myContext)) { + return false; + } } } return true; @@ -584,7 +593,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @Override public PyFunction findMethodByName(@Nullable final String name, boolean inherited, @Nullable TypeEvalContext context) { if (name == null) return null; - NameFinder proc = new NameFinder<>(name); + NameFinder proc = new NameFinder<>(notNullizeContext(context), name); visitMethods(proc, inherited, context); return proc.getResult(); } @@ -601,7 +610,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @Override public PyClass findNestedClass(String name, boolean inherited) { if (name == null) return null; - NameFinder proc = new NameFinder<>(name); + NameFinder proc = new NameFinder<>(TypeEvalContext.codeInsightFallback(getProject()), name); visitNestedClasses(proc, inherited); return proc.getResult(); } @@ -611,7 +620,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla public PyFunction findInitOrNew(boolean inherited, final @Nullable TypeEvalContext context) { NameFinder proc; if (isNewStyleClass(context)) { - proc = new NameFinder(PyNames.INIT, PyNames.NEW) { + proc = new NameFinder(notNullizeContext(context), PyNames.INIT, PyNames.NEW) { @Nullable @Override protected PyClass getContainingClass(@NotNull PyFunction element) { @@ -620,7 +629,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla }; } else { - proc = new NameFinder<>(PyNames.INIT); + proc = new NameFinder<>(notNullizeContext(context), PyNames.INIT); } visitMethods(proc, inherited, context); return proc.getResult(); @@ -1041,7 +1050,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @Override public PyTargetExpression findClassAttribute(@NotNull String name, boolean inherited, TypeEvalContext context) { - final NameFinder processor = new NameFinder<>(name); + final NameFinder processor = new NameFinder<>(notNullizeContext(context), name); visitClassAttributes(processor, inherited, context); return processor.getResult(); } diff --git a/python/testData/resolve/OverloadedDunderInit.py b/python/testData/resolve/OverloadedDunderInit.py new file mode 100644 index 000000000000..97d77bbd1f9d --- /dev/null +++ b/python/testData/resolve/OverloadedDunderInit.py @@ -0,0 +1,6 @@ +from typing import overload +class A: + @overload + def __init__(self, **kwargs): ... + def __init__(self, *args, **kwargs): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index cbc55a3acdcc..e3b4231c0345 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -15,6 +15,7 @@ import com.jetbrains.python.psi.resolve.ImportedResolveResult; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.PyClassTypeImpl; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.pyi.PyiUtil; public class PyResolveTest extends PyResolveTestCase { @Override @@ -1339,4 +1340,14 @@ public class PyResolveTest extends PyResolveTestCase { final PsiElement element = doResolve(); assertEquals(PyBuiltinCache.getInstance(myFixture.getFile()).getBuiltinsFile(), element); } + + // PY-35531 + public void testOverloadedDunderInit() { + final PyFile file = (PyFile)myFixture.configureByFile("resolve/" + getTestName(false) + ".py"); + final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file); + + final PyFunction function = file.findTopLevelClass("A").findInitOrNew(false, context); + assertNotNull(function); + assertFalse(PyiUtil.isOverload(function, context)); + } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index b8170d242de0..41a36e5f6d23 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -787,6 +787,21 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doMultiFileTest(); } + // PY-35531 + public void testAttributeDefinedInOverloadedDunderInit() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> doTestByText("from typing import overload\n" + + "class Example:\n" + + " @overload\n" + + " def __init__(self, **kwargs): ...\n" + + " def __init__(self, *args, **kwargs):\n" + + " self.__data = None\n" + + " def test(self):\n" + + " return self.__data") + ); + } + @NotNull @Override protected Class getInspectionClass() {