From 6f6b19252c0df20c6cab772d71085e146c47ea66 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 17 Dec 2018 17:55:33 +0300 Subject: [PATCH] Fix processing mixed dataclass hierarchy when non-dataclass has `__init__` (PY-28506, PY-31762, PY-33173) --- .../stdlib/PyDataclassTypeProvider.kt | 10 ++++++---- .../paramInfo/AttrsMixedHierarchyReplace.py | 17 ++++++++++++++++- .../DataclassesMixedHierarchyReplace/a.py | 17 ++++++++++++++++- .../InitializingAttrsMixedHierarchy.py | 17 ++++++++++++++++- .../InitializingDataclassMixedHierarchy/a.py | 17 ++++++++++++++++- .../jetbrains/python/PyParameterInfoTest.java | 14 ++++++++++---- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyDataclassTypeProvider.kt b/python/src/com/jetbrains/python/codeInsight/stdlib/PyDataclassTypeProvider.kt index 43ef3fde42cf..7eafa4bcd58c 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyDataclassTypeProvider.kt +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyDataclassTypeProvider.kt @@ -115,10 +115,12 @@ class PyDataclassTypeProvider : PyTypeProviderBase() { var seenInit = false for (currentType in StreamEx.of(clsType).append(cls.getAncestorTypes(context))) { - if (currentType == null) break - if (!currentType.resolveMember(PyNames.INIT, null, AccessDirection.READ, resolveContext, false).isNullOrEmpty()) break - if (!currentType.resolveMember(PyNames.NEW, null, AccessDirection.READ, resolveContext, false).isNullOrEmpty()) break - if (currentType !is PyClassType) break + if (currentType == null || + !currentType.resolveMember(PyNames.INIT, null, AccessDirection.READ, resolveContext, false).isNullOrEmpty() || + !currentType.resolveMember(PyNames.NEW, null, AccessDirection.READ, resolveContext, false).isNullOrEmpty() || + currentType !is PyClassType) { + if (seenInit) continue else break + } val current = currentType.pyClass if (PyKnownDecoratorUtil.hasUnknownDecorator(current, context)) break diff --git a/python/testData/paramInfo/AttrsMixedHierarchyReplace.py b/python/testData/paramInfo/AttrsMixedHierarchyReplace.py index 34fbca1c2e7b..c98fa8916114 100644 --- a/python/testData/paramInfo/AttrsMixedHierarchyReplace.py +++ b/python/testData/paramInfo/AttrsMixedHierarchyReplace.py @@ -39,4 +39,19 @@ class A4: class B4(A4): b: str -evolve(B4(), ) \ No newline at end of file +evolve(B4(), ) + + +@dataclass +class A5: + x: int + +class B5(A5): + def __init__(self): + pass + +@dataclass +class C5(B5): + z: str + +evolve(C5(1, "2"), ) \ No newline at end of file diff --git a/python/testData/paramInfo/DataclassesMixedHierarchyReplace/a.py b/python/testData/paramInfo/DataclassesMixedHierarchyReplace/a.py index 17dc32a16ec9..9b0332cd85e7 100644 --- a/python/testData/paramInfo/DataclassesMixedHierarchyReplace/a.py +++ b/python/testData/paramInfo/DataclassesMixedHierarchyReplace/a.py @@ -39,4 +39,19 @@ class A4: class B4(A4): b: str -replace(B4(), ) \ No newline at end of file +replace(B4(), ) + + +@dataclass +class A5: + x: int + +class B5(A5): + def __init__(self): + pass + +@dataclass +class C5(B5): + z: str + +replace(C5(1, "2"), ) \ No newline at end of file diff --git a/python/testData/paramInfo/InitializingAttrsMixedHierarchy.py b/python/testData/paramInfo/InitializingAttrsMixedHierarchy.py index 1834bfd69048..162a6cb1712e 100644 --- a/python/testData/paramInfo/InitializingAttrsMixedHierarchy.py +++ b/python/testData/paramInfo/InitializingAttrsMixedHierarchy.py @@ -39,4 +39,19 @@ class A4: class B4(A4): b: str -B4() \ No newline at end of file +B4() + + +@dataclass +class A5: + x: int + +class B5(A5): + def __init__(self): + pass + +@dataclass +class C5(B5): + z: str + +C5() \ No newline at end of file diff --git a/python/testData/paramInfo/InitializingDataclassMixedHierarchy/a.py b/python/testData/paramInfo/InitializingDataclassMixedHierarchy/a.py index ba19f394e646..3ec05fdcc912 100644 --- a/python/testData/paramInfo/InitializingDataclassMixedHierarchy/a.py +++ b/python/testData/paramInfo/InitializingDataclassMixedHierarchy/a.py @@ -39,4 +39,19 @@ class A4: class B4(A4): b: str -B4() \ No newline at end of file +B4() + + +@dataclass +class A5: + x: int + +class B5(A5): + def __init__(self): + pass + +@dataclass +class C5(B5): + z: str + +C5() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 16542a8715b3..314d72343513 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -786,7 +786,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { runWithLanguageLevel( LanguageLevel.PYTHON37, () -> { - final Map marks = loadMultiFileTest(4); + final Map marks = loadMultiFileTest(5); feignCtrlP(marks.get("").getTextOffset()).check("a: int", new String[]{"a: int"}); feignCtrlP(marks.get("").getTextOffset()).check("b: str", new String[]{"b: str"}); @@ -795,6 +795,8 @@ public class PyParameterInfoTest extends LightMarkedTestCase { feignCtrlP(marks.get("").getTextOffset()).check(Arrays.asList("self: object", "cls: object"), Arrays.asList(ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_STRING_ARRAY), Arrays.asList(new String[]{"self: object"}, new String[]{"cls: object"})); + + feignCtrlP(marks.get("").getTextOffset()).check("x: int, z: str", new String[]{"x: int, "}); } ); } @@ -871,7 +873,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { runWithLanguageLevel( LanguageLevel.PYTHON37, () -> { - final Map marks = loadTest(4); + final Map marks = loadTest(5); feignCtrlP(marks.get("").getTextOffset()).check("a: int", new String[]{"a: int"}); feignCtrlP(marks.get("").getTextOffset()).check("b: str", new String[]{"b: str"}); @@ -880,6 +882,8 @@ public class PyParameterInfoTest extends LightMarkedTestCase { feignCtrlP(marks.get("").getTextOffset()).check(Arrays.asList("self: object", "cls: object"), Arrays.asList(ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_STRING_ARRAY), Arrays.asList(new String[]{"self: object"}, new String[]{"cls: object"})); + + feignCtrlP(marks.get("").getTextOffset()).check("x: int, z: str", new String[]{"x: int, "}); } ); } @@ -920,12 +924,13 @@ public class PyParameterInfoTest extends LightMarkedTestCase { runWithLanguageLevel( LanguageLevel.PYTHON37, () -> { - final Map marks = loadMultiFileTest(4); + final Map marks = loadMultiFileTest(5); feignCtrlP(marks.get("").getTextOffset()).check("obj: B1, *, a: int=...", ArrayUtil.EMPTY_STRING_ARRAY); feignCtrlP(marks.get("").getTextOffset()).check("obj: B2, *, b: str=...", ArrayUtil.EMPTY_STRING_ARRAY); feignCtrlP(marks.get("").getTextOffset()).check("obj, **changes", new String[]{"**changes"}); feignCtrlP(marks.get("").getTextOffset()).check("obj, **changes", new String[]{"**changes"}); + feignCtrlP(marks.get("").getTextOffset()).check("obj: C5, *, x: int=..., z: str=...", ArrayUtil.EMPTY_STRING_ARRAY); } ); } @@ -975,12 +980,13 @@ public class PyParameterInfoTest extends LightMarkedTestCase { runWithLanguageLevel( LanguageLevel.PYTHON37, () -> { - final Map marks = loadTest(4); + final Map marks = loadTest(5); feignCtrlP(marks.get("").getTextOffset()).check("inst: B1, *, a: int=...", ArrayUtil.EMPTY_STRING_ARRAY); feignCtrlP(marks.get("").getTextOffset()).check("inst: B2, *, b: str=...", ArrayUtil.EMPTY_STRING_ARRAY); feignCtrlP(marks.get("").getTextOffset()).check("inst: _T, **changes", new String[]{"**changes"}); feignCtrlP(marks.get("").getTextOffset()).check("inst: _T, **changes", new String[]{"**changes"}); + feignCtrlP(marks.get("").getTextOffset()).check("inst: C5, *, x: int=..., z: str=...", ArrayUtil.EMPTY_STRING_ARRAY); } ); }