mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Fix processing attributes that override ancestors' ones (PY-28506, PY-31762)
This commit is contained in:
@@ -15,7 +15,6 @@ import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassFieldStub
|
||||
import com.jetbrains.python.psi.types.*
|
||||
import one.util.streamex.StreamEx
|
||||
import java.util.*
|
||||
|
||||
class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
|
||||
@@ -112,8 +111,7 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
val resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context)
|
||||
val ellipsis = PyElementGenerator.getInstance(cls.project).createEllipsis()
|
||||
|
||||
val collected = LinkedList<PyCallableParameter>()
|
||||
val seen = mutableSetOf<String?>()
|
||||
val collected = linkedMapOf<String, PyCallableParameter>()
|
||||
var seenInit = false
|
||||
|
||||
for (currentType in StreamEx.of(clsType).append(cls.getAncestorTypes(context))) {
|
||||
@@ -132,16 +130,27 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
current
|
||||
.classAttributes
|
||||
.asReversed()
|
||||
.filterNot { it.name in seen || PyTypingTypeProvider.isClassVar(it, context) }
|
||||
.asSequence()
|
||||
.filterNot { PyTypingTypeProvider.isClassVar(it, context) }
|
||||
.mapNotNull { fieldToParameter(cls, it, parameters.type, ellipsis, context) }
|
||||
.forEach {
|
||||
seen += it.name
|
||||
collected.addFirst(it)
|
||||
.forEach { parameter ->
|
||||
parameter.name?.let {
|
||||
// note: attributes are visited from inheritors to ancestors, in reversed order for every of them
|
||||
|
||||
if (parameters.type == PyDataclassParameters.Type.STD) {
|
||||
// std: attribute that overrides ancestor's attribute does not change the order but updates type
|
||||
collected[it] = collected.remove(it) ?: parameter
|
||||
}
|
||||
else if (!collected.containsKey(it)) {
|
||||
// attrs: attribute that overrides ancestor's attribute changes the order
|
||||
collected[it] = parameter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (seenInit) PyCallableTypeImpl(collected, clsType.toInstance()) else null
|
||||
return if (seenInit) PyCallableTypeImpl(collected.values.reversed(), clsType.toInstance()) else null
|
||||
}
|
||||
|
||||
private fun fieldToParameter(cls: PyClass,
|
||||
|
||||
@@ -41,4 +41,17 @@ class A4:
|
||||
class B4(A4):
|
||||
b: str
|
||||
|
||||
evolve(B4(), <arg4>)
|
||||
evolve(B4(), <arg4>)
|
||||
|
||||
|
||||
@dataclass
|
||||
class A5:
|
||||
x: Any = 15.0
|
||||
y: int = 0
|
||||
|
||||
@dataclass
|
||||
class B5(A5):
|
||||
z: int = 10
|
||||
x: int = 15
|
||||
|
||||
evolve(B5(), <arg5>)
|
||||
@@ -41,4 +41,17 @@ class A4:
|
||||
class B4(A4):
|
||||
b: str
|
||||
|
||||
replace(B4(), <arg4>)
|
||||
replace(B4(), <arg4>)
|
||||
|
||||
|
||||
@dataclass
|
||||
class A5:
|
||||
x: Any = 15.0
|
||||
y: int = 0
|
||||
|
||||
@dataclass
|
||||
class B5(A5):
|
||||
z: int = 10
|
||||
x: int = 15
|
||||
|
||||
replace(B5(), <arg5>)
|
||||
@@ -41,4 +41,17 @@ class A4:
|
||||
class B4(A4):
|
||||
b: str
|
||||
|
||||
B4(<arg4>)
|
||||
B4(<arg4>)
|
||||
|
||||
|
||||
@dataclass
|
||||
class A5:
|
||||
x: Any = 15.0
|
||||
y: int = 0
|
||||
|
||||
@dataclass
|
||||
class B5(A5):
|
||||
z: int = 10
|
||||
x: int = 15
|
||||
|
||||
B5(<arg5>)
|
||||
@@ -41,4 +41,17 @@ class A4:
|
||||
class B4(A4):
|
||||
b: str
|
||||
|
||||
B4(<arg4>)
|
||||
B4(<arg4>)
|
||||
|
||||
|
||||
@dataclass
|
||||
class A5:
|
||||
x: Any = 15.0
|
||||
y: int = 0
|
||||
|
||||
@dataclass
|
||||
class B5(A5):
|
||||
z: int = 10
|
||||
x: int = 15
|
||||
|
||||
B5(<arg5>)
|
||||
@@ -766,7 +766,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON37,
|
||||
() -> {
|
||||
final Map<String, PsiElement> marks = loadMultiFileTest(4);
|
||||
final Map<String, PsiElement> marks = loadMultiFileTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a: int, b: str", new String[]{"a: int, "});
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a: int, b: str", new String[]{"a: int, "});
|
||||
@@ -775,6 +775,8 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg4>").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("<arg5>").getTextOffset()).check("x: int=15, y: int=0, z: int=10", new String[]{"x: int=15, "});
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -842,12 +844,12 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
|
||||
// PY-31762
|
||||
public void testInitializingAttrsHierarchy() {
|
||||
// same as for std dataclasses
|
||||
// same as for std dataclasses except overridding
|
||||
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON37,
|
||||
() -> {
|
||||
final Map<String, PsiElement> marks = loadTest(4);
|
||||
final Map<String, PsiElement> marks = loadTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a: int, b: str", new String[]{"a: int, "});
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a: int, b: str", new String[]{"a: int, "});
|
||||
@@ -856,6 +858,8 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg4>").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("<arg5>").getTextOffset()).check("y: int=0, z: int=10, x: int=15", new String[]{"y: int=0, "});
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -900,12 +904,13 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON37,
|
||||
() -> {
|
||||
final Map<String, PsiElement> marks = loadMultiFileTest(4);
|
||||
final Map<String, PsiElement> marks = loadMultiFileTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("obj: B1, *, a: int=..., b: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("obj: B2, *, a: int=..., b: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("obj: B3, *, a: int=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("obj, **changes", new String[]{"**changes"});
|
||||
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("obj: B5, *, x: int=15, y: int=0, z: int=10", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -946,17 +951,19 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
|
||||
// PY-31762
|
||||
public void testAttrsHierarchyReplace() {
|
||||
// same as for std dataclasses
|
||||
// same as for std dataclasses except overridding
|
||||
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON37,
|
||||
() -> {
|
||||
final Map<String, PsiElement> marks = loadTest(4);
|
||||
final Map<String, PsiElement> marks = loadTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("inst: B1, *, a: int=..., b: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("inst: B2, *, a: int=..., b: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("inst: B3, *, a: int=...", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("inst: _T, **changes", new String[]{"**changes"});
|
||||
|
||||
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("inst: B5, *, y: int=0, z: int=10, x: int=15", ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user