mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Update parsing of attr.s for all the changes introduced in 19.2.0 and before (PY-36113, PY-38400)
GitOrigin-RevId: 11db701c16986bc8704f139f60a85f5b9c32a509
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ef89b40dd5
commit
9ef2a6f638
@@ -26,7 +26,8 @@ const val DUNDER_POST_INIT: String = "__post_init__"
|
||||
const val DUNDER_ATTRS_POST_INIT: String = "__attrs_post_init__"
|
||||
|
||||
private val STD_PARAMETERS = listOf("init", "repr", "eq", "order", "unsafe_hash", "frozen")
|
||||
private val ATTRS_PARAMETERS = listOf("these", "repr_ns", "repr", "cmp", "hash", "init", "slots", "frozen", "str", "auto_attribs")
|
||||
private val ATTRS_PARAMETERS = listOf("these", "repr_ns", "repr", "cmp", "hash", "init", "slots", "frozen", "weakref_slot", "str",
|
||||
"auto_attribs", "kw_only", "cache_hash", "auto_exc", "eq", "order")
|
||||
|
||||
/**
|
||||
* It should be used only to map arguments to parameters and
|
||||
@@ -65,16 +66,15 @@ fun parseDataclassParameters(cls: PyClass, context: TypeEvalContext): PyDataclas
|
||||
* @see parseStdDataclassParameters
|
||||
* @see parseDataclassParameters
|
||||
*/
|
||||
fun parseDataclassParametersForStub(cls: PyClass): PyDataclassParameters? = parseDataclassParametersFromAST(
|
||||
cls, null)
|
||||
fun parseDataclassParametersForStub(cls: PyClass): PyDataclassParameters? = parseDataclassParametersFromAST(cls, null)
|
||||
|
||||
fun resolvesToOmittedDefault(expression: PyExpression, type: PyDataclassParameters.Type): Boolean {
|
||||
fun resolvesToOmittedDefault(expression: PyExpression, type: Type): Boolean {
|
||||
if (expression is PyReferenceExpression) {
|
||||
val qNames = PyResolveUtil.resolveImportedElementQNameLocally(expression)
|
||||
|
||||
return when (type) {
|
||||
PyDataclassParameters.Type.STD -> QualifiedName.fromComponents("dataclasses", "MISSING") in qNames
|
||||
PyDataclassParameters.Type.ATTRS -> QualifiedName.fromComponents("attr", "NOTHING") in qNames
|
||||
Type.STD -> QualifiedName.fromComponents("dataclasses", "MISSING") in qNames
|
||||
Type.ATTRS -> QualifiedName.fromComponents("attr", "NOTHING") in qNames
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ fun resolvesToOmittedDefault(expression: PyExpression, type: PyDataclassParamete
|
||||
* It should be used only to map arguments to parameters and
|
||||
* determine what settings dataclass has.
|
||||
*/
|
||||
private fun decoratorAndTypeAndMarkedCallee(project: Project): List<Triple<PyKnownDecoratorUtil.KnownDecorator, PyDataclassParameters.Type, List<PyCallableParameter>>> {
|
||||
private fun decoratorAndTypeAndMarkedCallee(project: Project): List<Triple<KnownDecorator, Type, List<PyCallableParameter>>> {
|
||||
val generator = PyElementGenerator.getInstance(project)
|
||||
val ellipsis = generator.createEllipsis()
|
||||
|
||||
@@ -118,19 +118,16 @@ private fun parseDataclassParametersFromAST(cls: PyClass, context: TypeEvalConte
|
||||
context ?: TypeEvalContext.codeInsightFallback(cls.project)
|
||||
)
|
||||
|
||||
if (mapping.unmappedArguments.isEmpty() && mapping.unmappedParameters.isEmpty()) {
|
||||
val builder = PyDataclassParametersBuilder(decoratorAndTypeAndMarkedCallee.second,
|
||||
decoratorAndTypeAndMarkedCallee.first, cls)
|
||||
val builder = PyDataclassParametersBuilder(decoratorAndTypeAndMarkedCallee.second, decoratorAndTypeAndMarkedCallee.first, cls)
|
||||
|
||||
mapping
|
||||
.mappedParameters
|
||||
.entries
|
||||
.forEach {
|
||||
builder.update(it.value.name, it.key)
|
||||
}
|
||||
mapping
|
||||
.mappedParameters
|
||||
.entries
|
||||
.forEach {
|
||||
builder.update(it.value.name, it.key)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +178,7 @@ data class PyDataclassParameters(val init: Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
private class PyDataclassParametersBuilder(private val type: PyDataclassParameters.Type,
|
||||
private class PyDataclassParametersBuilder(private val type: Type,
|
||||
decorator: KnownDecorator,
|
||||
anchor: PsiElement) {
|
||||
|
||||
@@ -197,7 +194,7 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
private var init = DEFAULT_INIT
|
||||
private var repr = DEFAULT_REPR
|
||||
private var eq = DEFAULT_EQ
|
||||
private var order = if (type == PyDataclassParameters.Type.ATTRS) DEFAULT_EQ else DEFAULT_ORDER
|
||||
private var order = if (type == Type.ATTRS) DEFAULT_EQ else DEFAULT_ORDER
|
||||
private var unsafeHash = DEFAULT_UNSAFE_HASH
|
||||
private var frozen = DEFAULT_FROZEN
|
||||
|
||||
@@ -211,7 +208,7 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
private val others = mutableMapOf<String, PyExpression>()
|
||||
|
||||
init {
|
||||
if (type == PyDataclassParameters.Type.ATTRS && decorator == KnownDecorator.ATTR_DATACLASS) {
|
||||
if (type == Type.ATTRS && decorator == KnownDecorator.ATTR_DATACLASS) {
|
||||
PyElementGenerator.getInstance(anchor.project)
|
||||
.createExpressionFromText(LanguageLevel.forElement(anchor), PyNames.TRUE)
|
||||
.also { others["auto_attribs"] = it }
|
||||
@@ -233,14 +230,13 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
return
|
||||
}
|
||||
"frozen" -> {
|
||||
frozen = PyEvaluator.evaluateAsBoolean(value,
|
||||
DEFAULT_FROZEN)
|
||||
frozen = PyEvaluator.evaluateAsBoolean(value, DEFAULT_FROZEN)
|
||||
frozenArgument = argument
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (type == PyDataclassParameters.Type.STD) {
|
||||
if (type == Type.STD) {
|
||||
when (name) {
|
||||
"eq" -> {
|
||||
eq = PyEvaluator.evaluateAsBoolean(value, DEFAULT_EQ)
|
||||
@@ -248,21 +244,34 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
return
|
||||
}
|
||||
"order" -> {
|
||||
order = PyEvaluator.evaluateAsBoolean(value,
|
||||
DEFAULT_ORDER)
|
||||
order = PyEvaluator.evaluateAsBoolean(value, DEFAULT_ORDER)
|
||||
orderArgument = argument
|
||||
return
|
||||
}
|
||||
"unsafe_hash" -> {
|
||||
unsafeHash = PyEvaluator.evaluateAsBoolean(value,
|
||||
DEFAULT_UNSAFE_HASH)
|
||||
unsafeHash = PyEvaluator.evaluateAsBoolean(value, DEFAULT_UNSAFE_HASH)
|
||||
unsafeHashArgument = argument
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == PyDataclassParameters.Type.ATTRS) {
|
||||
else if (type == Type.ATTRS) {
|
||||
when (name) {
|
||||
"eq" -> {
|
||||
eq = PyEvaluator.evaluateAsBoolean(value, DEFAULT_EQ)
|
||||
eqArgument = argument
|
||||
|
||||
if (orderArgument == null) {
|
||||
order = eq
|
||||
orderArgument = eqArgument
|
||||
}
|
||||
}
|
||||
"order" -> {
|
||||
if (argument !is PyNoneLiteralExpression) {
|
||||
order = PyEvaluator.evaluateAsBoolean(value, DEFAULT_EQ)
|
||||
orderArgument = argument
|
||||
}
|
||||
}
|
||||
"cmp" -> {
|
||||
eq = PyEvaluator.evaluateAsBoolean(value, DEFAULT_EQ)
|
||||
eqArgument = argument
|
||||
@@ -272,8 +281,7 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
return
|
||||
}
|
||||
"hash" -> {
|
||||
unsafeHash = PyEvaluator.evaluateAsBoolean(value,
|
||||
DEFAULT_UNSAFE_HASH)
|
||||
unsafeHash = PyEvaluator.evaluateAsBoolean(value, DEFAULT_UNSAFE_HASH)
|
||||
unsafeHashArgument = argument
|
||||
return
|
||||
}
|
||||
@@ -286,7 +294,6 @@ private class PyDataclassParametersBuilder(private val type: PyDataclassParamete
|
||||
}
|
||||
|
||||
fun build() = PyDataclassParameters(init, repr, eq, order, unsafeHash, frozen,
|
||||
initArgument, reprArgument, eqArgument, orderArgument,
|
||||
unsafeHashArgument, frozenArgument,
|
||||
type, others)
|
||||
initArgument, reprArgument, eqArgument, orderArgument, unsafeHashArgument, frozenArgument,
|
||||
type, others)
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
|
||||
if (dataclassParameters.order && cmpMethods.isNotEmpty()) {
|
||||
cmpMethods.forEach { problems.add(it to "cmp") }
|
||||
cmpMethods.forEach { problems.add(it to "cmp/order") }
|
||||
}
|
||||
|
||||
if (dataclassParameters.frozen && mutatingMethods.isNotEmpty()) {
|
||||
@@ -407,7 +407,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
|
||||
if (dataclassParameters.order && dataclassParameters.frozen && hashMethod != null) {
|
||||
registerProblem(hashMethod?.nameIdentifier,
|
||||
"'${PyNames.HASH}' is ignored if the class already defines 'cmp' and 'frozen' parameters",
|
||||
"'${PyNames.HASH}' is ignored if the class already defines 'cmp/order' and 'frozen' parameters",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ print(hash(Derived1()))
|
||||
@attr.s(frozen=True)
|
||||
class A3:
|
||||
|
||||
def <warning descr="'__hash__' is ignored if the class already defines 'cmp' and 'frozen' parameters">__hash__</warning>(self):
|
||||
def <warning descr="'__hash__' is ignored if the class already defines 'cmp/order' and 'frozen' parameters">__hash__</warning>(self):
|
||||
pass
|
||||
|
||||
print(hash(A3()))
|
||||
|
||||
@@ -2,7 +2,7 @@ import attr
|
||||
|
||||
|
||||
@attr.dataclass(repr=True, cmp=True, str=True)
|
||||
class A:
|
||||
class A1:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__repr__' is ignored if the class already defines 'repr' parameter">__repr__</warning>(self):
|
||||
@@ -11,7 +11,15 @@ class A:
|
||||
def <warning descr="'__str__' is ignored if the class already defines 'str' parameter">__str__</warning>(self):
|
||||
return "str1"
|
||||
|
||||
def <warning descr="'__eq__' is ignored if the class already defines 'cmp' parameter">__eq__</warning>(self, other):
|
||||
def <warning descr="'__eq__' is ignored if the class already defines 'cmp/order' parameter">__eq__</warning>(self, other):
|
||||
return "eq1"
|
||||
|
||||
|
||||
@attr.dataclass(eq=True)
|
||||
class A2:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__eq__' is ignored if the class already defines 'cmp/order' parameter">__eq__</warning>(self, other):
|
||||
return "eq1"
|
||||
|
||||
|
||||
@@ -51,12 +59,13 @@ class B2:
|
||||
class C1:
|
||||
c: int = 1
|
||||
|
||||
def <warning descr="'__eq__' is ignored if the class already defines 'cmp' parameter">__eq__</warning>(self, other):
|
||||
def <warning descr="'__eq__' is ignored if the class already defines 'cmp/order' parameter">__eq__</warning>(self, other):
|
||||
return "eq1"
|
||||
|
||||
print(repr(A()))
|
||||
print(str(A()))
|
||||
print(A() == A())
|
||||
print(repr(A1()))
|
||||
print(str(A1()))
|
||||
print(A1() == A1())
|
||||
print(A2() == A2())
|
||||
|
||||
print(repr(Derived()))
|
||||
print(str(Derived()))
|
||||
|
||||
@@ -5,7 +5,7 @@ import attr
|
||||
class A1:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__le__' is ignored if the class already defines 'cmp' parameter">__le__</warning>(self, other):
|
||||
def <warning descr="'__le__' is ignored if the class already defines 'cmp/order' parameter">__le__</warning>(self, other):
|
||||
return "le1"
|
||||
|
||||
print(A1(1) <= A1(2))
|
||||
@@ -27,7 +27,7 @@ print(Derived1(1) <= Derived1(2))
|
||||
class A2:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__lt__' is ignored if the class already defines 'cmp' parameter">__lt__</warning>(self, other):
|
||||
def <warning descr="'__lt__' is ignored if the class already defines 'cmp/order' parameter">__lt__</warning>(self, other):
|
||||
return "lt1"
|
||||
|
||||
print(A2(1) < A2(2))
|
||||
@@ -49,7 +49,7 @@ print(Derived2(1) < Derived2(2))
|
||||
class A3:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__gt__' is ignored if the class already defines 'cmp' parameter">__gt__</warning>(self, other):
|
||||
def <warning descr="'__gt__' is ignored if the class already defines 'cmp/order' parameter">__gt__</warning>(self, other):
|
||||
return "gt1"
|
||||
|
||||
print(A3(1) > A3(2))
|
||||
@@ -71,7 +71,7 @@ print(Derived3(1) > Derived3(2))
|
||||
class A4:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__ge__' is ignored if the class already defines 'cmp' parameter">__ge__</warning>(self, other):
|
||||
def <warning descr="'__ge__' is ignored if the class already defines 'cmp/order' parameter">__ge__</warning>(self, other):
|
||||
return "ge1"
|
||||
|
||||
print(A4(1) >= A4(2))
|
||||
@@ -86,4 +86,34 @@ class Base4:
|
||||
class Derived4(Base4):
|
||||
d: int = 1
|
||||
|
||||
print(Derived4(1) >= Derived4(2))
|
||||
print(Derived4(1) >= Derived4(2))
|
||||
|
||||
|
||||
@attr.dataclass(eq=True)
|
||||
class A5:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__le__' is ignored if the class already defines 'cmp/order' parameter">__le__</warning>(self, other):
|
||||
return "le5"
|
||||
|
||||
print(A5(1) <= A5(2))
|
||||
|
||||
|
||||
@attr.dataclass(eq=True, order=None)
|
||||
class A6:
|
||||
a: int = 1
|
||||
|
||||
def <warning descr="'__le__' is ignored if the class already defines 'cmp/order' parameter">__le__</warning>(self, other):
|
||||
return "le6"
|
||||
|
||||
print(A6(1) <= A6(2))
|
||||
|
||||
|
||||
@attr.dataclass(eq=True, order=False)
|
||||
class A7:
|
||||
a: int = 1
|
||||
|
||||
def __le__(self, other):
|
||||
return "le7"
|
||||
|
||||
print(A7(1) <= A7(2))
|
||||
@@ -70,4 +70,11 @@ class F1:
|
||||
foo = "bar" # <- has no type annotation, so doesn't count.
|
||||
baz: str
|
||||
|
||||
F1(<arg8>)
|
||||
F1(<arg8>)
|
||||
|
||||
|
||||
@attr.dataclass(unknown=True)
|
||||
class G1:
|
||||
bar: str
|
||||
|
||||
G1(<arg9>)
|
||||
@@ -795,7 +795,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON36,
|
||||
() -> {
|
||||
final Map<String, PsiElement> marks = loadTest(8);
|
||||
final Map<String, PsiElement> marks = loadTest(9);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("x: int, y: str, z: float=0.0", new String[]{"x: int, "});
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("x: int, y: str, z: float=0.0", new String[]{"x: int, "});
|
||||
@@ -811,6 +811,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg6>").getTextOffset()).check("x: int, y: str=\"0\"", new String[]{"x: int, "});
|
||||
feignCtrlP(marks.get("<arg7>").getTextOffset()).check("x: int", new String[]{"x: int"});
|
||||
feignCtrlP(marks.get("<arg8>").getTextOffset()).check("baz: str", new String[]{"baz: str"});
|
||||
feignCtrlP(marks.get("<arg9>").getTextOffset()).check("bar: str", new String[]{"bar: str"});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user