mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 15:52:01 +07:00
PY-54560 Support PEP-681 dataclass_transform
`dataclass_transform` support posed a number of challenges to the current AST/PSI stubs separation in our architecture. For the standard "dataclasses" module and the "attrs" package API, we could rely on well-known names and defaults to recognize and preserve the information about decorator arguments and field specifier arguments in PSI stubs. With `dataclass_transform` however, effectively any decorator call or extending any base class can indicate using a "magical" API that should generate dataclass-like entities. At the moment of building PSI stubs we can't be sure, because we can't leave the boundaries of the current file to resolve these names and determine if these decorators and base classes are decorated themselves with `dataclass_transform`. To support that, we instead rely on well-known keyword argument names documented in the spec, e.g. "kw_only" or "frozen". In other words, whenever we encounter any decorator call or a superclass list with such keyword arguments, we generate a `PyDataclassStub` stub for the corresponding class. The same thing is happening with class attribute initializers, i.e. whenever we see a function call with argument such as "default" or "kw_only" in their RHS, we generate a `PyDataclassFieldStub` for the corresponding target expression. Both of these stub interfaces now can contain null values for the corresponding properties if they were not specified directly in the class definition. Finally, for the `dataclass_transform` decorator itself, a new custom decorator stub was introduced -- `PyDataclassTransformDecoratorStub`, it preserves its keyword arguments, such as "keyword_only_default" or "frozen_default" controlling the default properties of generated dataclasses. Later, when we need concluded information about specific dataclass properties, e.g. in `PyDataclassTypeProvider` to generate a constructor signature, or in `PyDataclassInspection`, we try to "resolve" this incomplete information from stubs into finalized `PyDataclassParameters` and `PyDataclassFieldParameters` that contain non-null versions of the same fields. The main entry points for that are `resolveDataclassParameters` and `resolveDataclassFieldParameters`. These methods additionally handle the situations where decorators, superclass lists and field specifiers lack any keyword arguments, and thus, there were no automatically created custom stubs for them. All the existing usages of `PyDataclassStub` and `PyDataclassFieldStub` were updated to operate on `PyDataclassParameters` and `PyDataclassFieldParameters` instead. Counterparts of the tests on various inspection checks for the standard dataclasses definitions were added for dataclasses created with `dataclass_transform`, even though the spec is unclear on some aspects the expected type checker semantics, e.g. if combining "eq=False" and "order=True" or specifying both "default" and "default_factory" for a field should be reported. I tried to follow common sense when enabling existing checks for such arbitrary user-defined dataclass APIs. GitOrigin-RevId: 4180a1e32b5e4025fc4e3ed49bb8d67af0d60e66
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a20dc6e783
commit
1da22d34fd
@@ -526,13 +526,9 @@
|
||||
<customClassStubType implementation="com.jetbrains.python.psi.impl.stubs.PyDataclassStubType"/>
|
||||
<customDecoratorStubType implementation="com.jetbrains.python.psi.stubs.PyTestFixtureDecoratorStubType"/>
|
||||
<customDecoratorStubType implementation="com.jetbrains.python.psi.stubs.PyFunctoolsWrapsDecoratorStubType"/>
|
||||
<customDecoratorStubType implementation="com.jetbrains.python.psi.stubs.PyDataclassTransformDecoratorStubType"/>
|
||||
|
||||
<typeProvider implementation="com.jetbrains.python.psi.types.PyCollectionTypeByModificationsProvider" order="last"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.decorator.PyDecoratedFunctionTypeProvider"
|
||||
id="pyDecoratedFunctionTypeProvider"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.decorator.PyFunctoolsWrapsDecoratedFunctionTypeProvider"
|
||||
order="before pyDecoratedFunctionTypeProvider"/>
|
||||
|
||||
|
||||
<!-- typing -->
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider"/>
|
||||
@@ -576,6 +572,10 @@
|
||||
<canonicalPathProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibCanonicalPathProvider"/>
|
||||
<inspectionExtension implementation="com.jetbrains.python.inspections.stdlib.PyStdlibInspectionExtension"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyDataclassTypeProvider"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.decorator.PyDecoratedFunctionTypeProvider"
|
||||
id="pyDecoratedFunctionTypeProvider"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.decorator.PyFunctoolsWrapsDecoratedFunctionTypeProvider"
|
||||
order="before pyDecoratedFunctionTypeProvider"/>
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyNamedTupleTypeProvider"/>
|
||||
<!-- This provider should be able to override the results on anything else, including types coming from .pyi stubs -->
|
||||
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibTypeProvider" order="first"/>
|
||||
|
||||
@@ -9,18 +9,21 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.codeInsight.PyDataclassParameters.Type
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
import com.jetbrains.python.psi.impl.StubAwareComputation
|
||||
import com.jetbrains.python.psi.impl.stubs.PyDataclassFieldStubImpl
|
||||
import com.jetbrains.python.psi.impl.stubs.PyDataclassStubImpl
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassFieldStub
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassStub
|
||||
import com.jetbrains.python.psi.types.PyCallableParameter
|
||||
import com.jetbrains.python.psi.types.PyCallableParameterImpl
|
||||
import com.jetbrains.python.psi.types.PyCallableTypeImpl
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassTransformDecoratorStub
|
||||
import com.jetbrains.python.psi.types.*
|
||||
|
||||
|
||||
object PyDataclassNames {
|
||||
@@ -86,6 +89,33 @@ object PyDataclassNames {
|
||||
"attrs.fields_dict",
|
||||
)
|
||||
}
|
||||
|
||||
object DataclassTransform {
|
||||
val DATACLASS_TRANSFORM_NAMES = setOf(
|
||||
"typing.dataclass_transform",
|
||||
"typing_extensions.dataclass_transform",
|
||||
)
|
||||
|
||||
val DECORATOR_OR_CLASS_PARAMETERS = setOf(
|
||||
"init",
|
||||
"eq",
|
||||
"order",
|
||||
"unsafe_hash",
|
||||
"frozen",
|
||||
"match_args",
|
||||
"kw_only",
|
||||
"slots",
|
||||
)
|
||||
|
||||
val FIELD_SPECIFIER_PARAMETERS = setOf(
|
||||
"init",
|
||||
"default",
|
||||
"default_factory",
|
||||
"factory",
|
||||
"kw_only",
|
||||
"alias",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,17 +136,25 @@ private val DECORATOR_AND_TYPE_AND_PARAMETERS = listOf(
|
||||
Triple(KnownDecorator.ATTRS_FROZEN, PyDataclassParameters.PredefinedType.ATTRS, PyDataclassNames.Attrs.DECORATOR_PARAMETERS),
|
||||
)
|
||||
|
||||
|
||||
fun parseStdDataclassParameters(cls: PyClass, context: TypeEvalContext): PyDataclassParameters? {
|
||||
return parseDataclassParameters(cls, context)?.takeIf { it.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD }
|
||||
}
|
||||
|
||||
fun parseStdOrDataclassTransformDataclassParameters(cls: PyClass, context: TypeEvalContext): PyDataclassParameters? {
|
||||
return parseDataclassParameters(cls, context)?.takeIf { it.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
it.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM }
|
||||
}
|
||||
|
||||
fun parseDataclassParameters(cls: PyClass, context: TypeEvalContext): PyDataclassParameters? {
|
||||
return PyUtil.getNullableParameterizedCachedValue(cls, context) {
|
||||
StubAwareComputation.on(cls)
|
||||
return@getNullableParameterizedCachedValue StubAwareComputation.on(cls)
|
||||
.withCustomStub { stub -> stub.getCustomStub(PyDataclassStub::class.java) }
|
||||
.overStub(::parseDataclassParametersFromStub)
|
||||
.overAst { parseDataclassParametersFromAST(it, context) }
|
||||
.overStub { dataclassStub -> resolveDataclassParameters(cls, dataclassStub ?: PyDataclassStubImpl.NON_PARAMETERIZED_DATACLASS_TRANSFORM_CANDIDATE_STUB, null, context) }
|
||||
.overAst {
|
||||
val (dataclassStub, dataclassParamArgMapping) = parseDataclassParametersFromAST(it, context)
|
||||
?: (PyDataclassStubImpl.NON_PARAMETERIZED_DATACLASS_TRANSFORM_CANDIDATE_STUB to null)
|
||||
return@overAst resolveDataclassParameters(cls, dataclassStub, dataclassParamArgMapping, context)
|
||||
}
|
||||
.withStubBuilder { PyDataclassStubImpl.create(it) }
|
||||
.compute(context)
|
||||
}
|
||||
@@ -128,14 +166,15 @@ fun parseDataclassParameters(cls: PyClass, context: TypeEvalContext): PyDataclas
|
||||
* @see parseStdDataclassParameters
|
||||
* @see parseDataclassParameters
|
||||
*/
|
||||
fun parseDataclassParametersForStub(cls: PyClass): PyDataclassParameters? = parseDataclassParametersFromAST(cls, null)
|
||||
fun parseDataclassParametersForStub(cls: PyClass): PyDataclassStub? = parseDataclassParametersFromAST(cls, null)?.first
|
||||
|
||||
fun resolvesToOmittedDefault(expression: PyExpression, type: Type): Boolean {
|
||||
if (expression is PyReferenceExpression) {
|
||||
val qNames = PyResolveUtil.resolveImportedElementQNameLocally(expression)
|
||||
|
||||
return when (type.asPredefinedType) {
|
||||
PyDataclassParameters.PredefinedType.STD -> qNames.any { it.toString() == PyDataclassNames.Dataclasses.DATACLASSES_MISSING }
|
||||
PyDataclassParameters.PredefinedType.STD, PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM ->
|
||||
qNames.any { it.toString() == PyDataclassNames.Dataclasses.DATACLASSES_MISSING }
|
||||
PyDataclassParameters.PredefinedType.ATTRS -> qNames.any { it.toString() in PyDataclassNames.Attrs.ATTRS_NOTHING }
|
||||
else -> false
|
||||
}
|
||||
@@ -166,36 +205,83 @@ private fun decoratorAndTypeAndMarkedCallee(project: Project): List<Triple<Quali
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDataclassParametersFromAST(cls: PyClass, context: TypeEvalContext?): PyDataclassParameters? {
|
||||
val decorators = cls.decoratorList ?: return null
|
||||
private fun parseDataclassParametersFromAST(cls: PyClass, context: TypeEvalContext?): Pair<PyDataclassStub, DataclassParameterArgumentMapping>? {
|
||||
val decorators = cls.decoratorList
|
||||
|
||||
val provided = PyDataclassParametersProvider.EP_NAME.extensionList.asSequence().mapNotNull {
|
||||
it.getDataclassParameters(cls, context)
|
||||
}.firstOrNull()
|
||||
if (provided != null) return provided
|
||||
|
||||
for (decorator in decorators.decorators) {
|
||||
val callee = (decorator.callee as? PyReferenceExpression) ?: continue
|
||||
|
||||
for (decoratorQualifiedName in PyResolveUtil.resolveImportedElementQNameLocally(callee)) {
|
||||
val types = decoratorAndTypeAndMarkedCallee(cls.project)
|
||||
val decoratorAndTypeAndMarkedCallee = types.firstOrNull { it.first == decoratorQualifiedName } ?: continue
|
||||
|
||||
val mapping = PyCallExpressionHelper.mapArguments(
|
||||
decorator,
|
||||
PyCallableTypeImpl(decoratorAndTypeAndMarkedCallee.third, null),
|
||||
context ?: TypeEvalContext.codeInsightFallback(cls.project)
|
||||
if (decorators != null) {
|
||||
val provided = PyDataclassParametersProvider.EP_NAME.extensionList.asSequence().mapNotNull {
|
||||
it.getDataclassParameters(cls, context)
|
||||
}.firstOrNull()
|
||||
if (provided != null) return Pair(
|
||||
PyDataclassStubImpl(
|
||||
type = provided.type.toString(),
|
||||
decoratorName = null,
|
||||
init = provided.init,
|
||||
repr = provided.repr,
|
||||
eq = provided.eq,
|
||||
order = provided.order,
|
||||
unsafeHash = provided.unsafeHash,
|
||||
frozen = provided.frozen,
|
||||
kwOnly = provided.kwOnly,
|
||||
),
|
||||
DataclassParameterArgumentMapping(
|
||||
initArgument = provided.initArgument,
|
||||
reprArgument = provided.reprArgument,
|
||||
eqArgument = provided.eqArgument,
|
||||
orderArgument = provided.orderArgument,
|
||||
unsafeHashArgument = provided.unsafeHashArgument,
|
||||
frozenArgument = provided.frozenArgument,
|
||||
kwOnlyArgument = provided.kwOnlyArgument,
|
||||
others = provided.others,
|
||||
)
|
||||
)
|
||||
|
||||
val builder = PyDataclassParametersBuilder(decoratorAndTypeAndMarkedCallee.second, decoratorAndTypeAndMarkedCallee.first, cls)
|
||||
|
||||
mapping
|
||||
.mappedParameters
|
||||
.entries
|
||||
.forEach {
|
||||
builder.update(it.value.name, it.key)
|
||||
for (decorator in decorators.decorators) {
|
||||
val callee = (decorator.callee as? PyReferenceExpression) ?: continue
|
||||
|
||||
for (decoratorQualifiedName in PyResolveUtil.resolveImportedElementQNameLocally(callee)) {
|
||||
val types = decoratorAndTypeAndMarkedCallee(cls.project)
|
||||
val decoratorAndTypeAndMarkedCallee = types.firstOrNull { it.first == decoratorQualifiedName } ?: continue
|
||||
|
||||
val mapping = PyCallExpressionHelper.mapArguments(
|
||||
decorator,
|
||||
PyCallableTypeImpl(decoratorAndTypeAndMarkedCallee.third, null),
|
||||
context ?: TypeEvalContext.codeInsightFallback(cls.project)
|
||||
)
|
||||
|
||||
val builder = PyDataclassParametersBuilder(decoratorAndTypeAndMarkedCallee.second, decoratorAndTypeAndMarkedCallee.first)
|
||||
|
||||
mapping
|
||||
.mappedParameters
|
||||
.entries
|
||||
.forEach {
|
||||
builder.update(it.value.name, it.key)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
// Process decorators that have dataclass_transform-compatible keyword arguments.
|
||||
if (decorator.qualifiedName == null) continue
|
||||
val decoratorKeywordArguments = decorator.arguments.filterIsInstance<PyKeywordArgument>()
|
||||
if (decoratorKeywordArguments.map { it.name }.any { it in PyDataclassNames.DataclassTransform.DECORATOR_OR_CLASS_PARAMETERS }) {
|
||||
val builder = PyDataclassParametersBuilder(PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM, decorator.qualifiedName!!)
|
||||
decoratorKeywordArguments.forEach {
|
||||
builder.update(it.keyword, it)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process dataclass_transform-compatible keyword argument in the list of superclasses.
|
||||
val superclassList = cls.superClassExpressionList
|
||||
if (superclassList != null) {
|
||||
val classKeywordArguments = superclassList.arguments.filterIsInstance<PyKeywordArgument>()
|
||||
if (classKeywordArguments.map { it.name }.any { it in PyDataclassNames.DataclassTransform.DECORATOR_OR_CLASS_PARAMETERS }) {
|
||||
val builder = PyDataclassParametersBuilder(PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM, null)
|
||||
classKeywordArguments.forEach {
|
||||
builder.update(it.keyword, it)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
@@ -203,21 +289,6 @@ private fun parseDataclassParametersFromAST(cls: PyClass, context: TypeEvalConte
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseDataclassParametersFromStub(stub: PyDataclassStub?): PyDataclassParameters? {
|
||||
return stub?.let {
|
||||
val type =
|
||||
PyDataclassParametersProvider.EP_NAME.extensionList.map { e -> e.getType() }.firstOrNull { t -> t.name == it.type }
|
||||
?: PyDataclassParameters.PredefinedType.values().firstOrNull { t -> t.name == it.type }
|
||||
?: PyDataclassParameters.PredefinedType.STD
|
||||
|
||||
PyDataclassParameters(
|
||||
it.initValue(), it.reprValue(), it.eqValue(), it.orderValue(), it.unsafeHashValue(), it.frozenValue(), it.kwOnly(),
|
||||
null, null, null, null, null, null, null,
|
||||
type, emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data describing dataclass.
|
||||
*
|
||||
@@ -227,22 +298,25 @@ private fun parseDataclassParametersFromStub(stub: PyDataclassStub?): PyDataclas
|
||||
* This class also describes [type] of the dataclass and
|
||||
* contains key-value pairs of other parameters and their expressions.
|
||||
*/
|
||||
data class PyDataclassParameters(val init: Boolean,
|
||||
val repr: Boolean,
|
||||
val eq: Boolean,
|
||||
val order: Boolean,
|
||||
val unsafeHash: Boolean,
|
||||
val frozen: Boolean,
|
||||
val kwOnly: Boolean,
|
||||
val initArgument: PyExpression?,
|
||||
val reprArgument: PyExpression?,
|
||||
val eqArgument: PyExpression?,
|
||||
val orderArgument: PyExpression?,
|
||||
val unsafeHashArgument: PyExpression?,
|
||||
val frozenArgument: PyExpression?,
|
||||
val kwOnlyArgument: PyExpression?,
|
||||
val type: Type,
|
||||
val others: Map<String, PyExpression>) {
|
||||
data class PyDataclassParameters(
|
||||
val init: Boolean,
|
||||
val repr: Boolean,
|
||||
val eq: Boolean,
|
||||
val order: Boolean,
|
||||
val unsafeHash: Boolean,
|
||||
val frozen: Boolean,
|
||||
val kwOnly: Boolean,
|
||||
val initArgument: PyExpression?,
|
||||
val reprArgument: PyExpression?,
|
||||
val eqArgument: PyExpression?,
|
||||
val orderArgument: PyExpression?,
|
||||
val unsafeHashArgument: PyExpression?,
|
||||
val frozenArgument: PyExpression?,
|
||||
val kwOnlyArgument: PyExpression?,
|
||||
val type: Type,
|
||||
val others: Map<String, PyExpression>,
|
||||
val fieldSpecifiers: List<QualifiedName> = emptyList()
|
||||
) {
|
||||
|
||||
interface Type {
|
||||
val name: String
|
||||
@@ -250,7 +324,7 @@ data class PyDataclassParameters(val init: Boolean,
|
||||
}
|
||||
|
||||
enum class PredefinedType : Type {
|
||||
STD, ATTRS;
|
||||
STD, ATTRS, DATACLASS_TRANSFORM;
|
||||
|
||||
override val asPredefinedType: PredefinedType? = this
|
||||
}
|
||||
@@ -270,25 +344,14 @@ interface PyDataclassParametersProvider {
|
||||
fun getDataclassParameters(cls: PyClass, context: TypeEvalContext?): PyDataclassParameters? = null
|
||||
}
|
||||
|
||||
private class PyDataclassParametersBuilder(private val type: Type, decorator: QualifiedName, anchor: PsiElement) {
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_INIT = true
|
||||
private const val DEFAULT_REPR = true
|
||||
private const val DEFAULT_EQ = true
|
||||
private const val DEFAULT_ORDER = false
|
||||
private const val DEFAULT_UNSAFE_HASH = false
|
||||
private const val DEFAULT_FROZEN = false
|
||||
private const val DEFAULT_KW_ONLY = false
|
||||
}
|
||||
|
||||
private var init = DEFAULT_INIT
|
||||
private var repr = DEFAULT_REPR
|
||||
private var eq = DEFAULT_EQ
|
||||
private var order = if (type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS) DEFAULT_EQ else DEFAULT_ORDER
|
||||
private var unsafeHash = DEFAULT_UNSAFE_HASH
|
||||
private var frozen = decorator.toString() in PyDataclassNames.Attrs.ATTRS_FROZEN || DEFAULT_FROZEN
|
||||
private var kwOnly = DEFAULT_KW_ONLY
|
||||
private class PyDataclassParametersBuilder(private val type: Type, private val decorator: QualifiedName?) {
|
||||
private var init: Boolean? = null
|
||||
private var repr: Boolean? = null
|
||||
private var eq: Boolean? = null
|
||||
private var order: Boolean? = null
|
||||
private var unsafeHash: Boolean? = null
|
||||
private var frozen: Boolean? = null
|
||||
private var kwOnly: Boolean? = null
|
||||
|
||||
private var initArgument: PyExpression? = null
|
||||
private var reprArgument: PyExpression? = null
|
||||
@@ -300,78 +363,73 @@ private class PyDataclassParametersBuilder(private val type: Type, decorator: Qu
|
||||
|
||||
private val others = mutableMapOf<String, PyExpression>()
|
||||
|
||||
init {
|
||||
if (type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS && decorator == KnownDecorator.ATTR_DATACLASS.qualifiedName) {
|
||||
PyElementGenerator.getInstance(anchor.project)
|
||||
.createExpressionFromText(LanguageLevel.forElement(anchor), PyNames.TRUE)
|
||||
.also { others["auto_attribs"] = it }
|
||||
}
|
||||
}
|
||||
|
||||
fun update(name: String?, argument: PyExpression?) {
|
||||
val value = PyUtil.peelArgument(argument)
|
||||
|
||||
when (name) {
|
||||
"init" -> {
|
||||
init = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_INIT
|
||||
init = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
initArgument = argument
|
||||
return
|
||||
}
|
||||
"repr" -> {
|
||||
repr = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_REPR
|
||||
repr = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
reprArgument = argument
|
||||
return
|
||||
}
|
||||
"frozen" -> {
|
||||
frozen = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_FROZEN
|
||||
frozen = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
frozenArgument = argument
|
||||
return
|
||||
}
|
||||
"kw_only" -> {
|
||||
kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
kwOnlyArgument = argument
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (type.asPredefinedType == PyDataclassParameters.PredefinedType.STD) {
|
||||
if (type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM) {
|
||||
when (name) {
|
||||
"eq" -> {
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_EQ
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
eqArgument = argument
|
||||
return
|
||||
}
|
||||
"order" -> {
|
||||
order = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_ORDER
|
||||
order = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
orderArgument = argument
|
||||
return
|
||||
}
|
||||
"unsafe_hash" -> {
|
||||
unsafeHash = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_UNSAFE_HASH
|
||||
unsafeHash = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
unsafeHashArgument = argument
|
||||
return
|
||||
}
|
||||
"kw_only" -> {
|
||||
kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_KW_ONLY
|
||||
kwOnlyArgument = argument
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS) {
|
||||
when (name) {
|
||||
"eq" -> {
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_EQ
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
eqArgument = argument
|
||||
|
||||
if (orderArgument == null) {
|
||||
if (orderArgument == null && eqArgument != null) {
|
||||
order = eq
|
||||
orderArgument = eqArgument
|
||||
}
|
||||
return
|
||||
}
|
||||
"order" -> {
|
||||
if (argument !is PyNoneLiteralExpression) {
|
||||
order = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_EQ
|
||||
order = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
orderArgument = argument
|
||||
}
|
||||
return
|
||||
}
|
||||
"cmp" -> {
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_EQ
|
||||
eq = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
eqArgument = argument
|
||||
|
||||
order = eq
|
||||
@@ -379,15 +437,10 @@ private class PyDataclassParametersBuilder(private val type: Type, decorator: Qu
|
||||
return
|
||||
}
|
||||
"hash" -> {
|
||||
unsafeHash = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_UNSAFE_HASH
|
||||
unsafeHash = PyEvaluator.evaluateAsBooleanNoResolve(value)
|
||||
unsafeHashArgument = argument
|
||||
return
|
||||
}
|
||||
"kw_only" -> {
|
||||
kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(value) ?: DEFAULT_KW_ONLY
|
||||
kwOnlyArgument = argument
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,9 +449,271 @@ private class PyDataclassParametersBuilder(private val type: Type, decorator: Qu
|
||||
}
|
||||
}
|
||||
|
||||
fun build() = PyDataclassParameters(
|
||||
init, repr, eq, order, unsafeHash, frozen, kwOnly,
|
||||
initArgument, reprArgument, eqArgument, orderArgument, unsafeHashArgument, frozenArgument, kwOnlyArgument,
|
||||
type, others
|
||||
fun build(): Pair<PyDataclassStub, DataclassParameterArgumentMapping> =
|
||||
Pair(
|
||||
PyDataclassStubImpl(
|
||||
type = type.name,
|
||||
decoratorName = decorator,
|
||||
init = init,
|
||||
repr = repr,
|
||||
eq = eq,
|
||||
order = order,
|
||||
unsafeHash = unsafeHash,
|
||||
frozen = frozen,
|
||||
kwOnly = kwOnly,
|
||||
),
|
||||
DataclassParameterArgumentMapping(
|
||||
initArgument = initArgument,
|
||||
reprArgument = reprArgument,
|
||||
eqArgument = eqArgument,
|
||||
orderArgument = orderArgument,
|
||||
unsafeHashArgument = unsafeHashArgument,
|
||||
frozenArgument = frozenArgument,
|
||||
kwOnlyArgument = kwOnlyArgument,
|
||||
others = others,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private data class DataclassParameterArgumentMapping(
|
||||
val initArgument: PyExpression?,
|
||||
val reprArgument: PyExpression?,
|
||||
val eqArgument: PyExpression?,
|
||||
val orderArgument: PyExpression?,
|
||||
val unsafeHashArgument: PyExpression?,
|
||||
val frozenArgument: PyExpression?,
|
||||
val kwOnlyArgument: PyExpression?,
|
||||
val others: Map<String, PyExpression>
|
||||
)
|
||||
|
||||
@Suppress("NullableBooleanElvis")
|
||||
private fun resolveDataclassParameters(
|
||||
pyClass: PyClass,
|
||||
stub: PyDataclassStub,
|
||||
argumentMapping: DataclassParameterArgumentMapping?,
|
||||
context: TypeEvalContext,
|
||||
): PyDataclassParameters? {
|
||||
|
||||
val type =
|
||||
PyDataclassParametersProvider.EP_NAME.extensionList.map { e -> e.getType() }.firstOrNull { t -> t.name == stub.type }
|
||||
?: PyDataclassParameters.PredefinedType.entries.firstOrNull { t -> t.name == stub.type }
|
||||
?: PyDataclassParameters.PredefinedType.STD
|
||||
|
||||
when (type.asPredefinedType) {
|
||||
PyDataclassParameters.PredefinedType.STD -> {
|
||||
return PyDataclassParameters(
|
||||
init = stub.initValue() ?: true,
|
||||
repr = stub.reprValue() ?: true,
|
||||
eq = stub.eqValue() ?: true,
|
||||
order = stub.orderValue() ?: false,
|
||||
unsafeHash = stub.unsafeHashValue() ?: false,
|
||||
frozen = stub.frozenValue() ?: false,
|
||||
kwOnly = stub.kwOnly() ?: false,
|
||||
initArgument = argumentMapping?.initArgument,
|
||||
reprArgument = argumentMapping?.reprArgument,
|
||||
eqArgument = argumentMapping?.eqArgument,
|
||||
orderArgument = argumentMapping?.orderArgument,
|
||||
unsafeHashArgument = argumentMapping?.unsafeHashArgument,
|
||||
frozenArgument = argumentMapping?.frozenArgument,
|
||||
kwOnlyArgument = argumentMapping?.kwOnlyArgument,
|
||||
others = argumentMapping?.others ?: emptyMap(),
|
||||
type = type,
|
||||
fieldSpecifiers = listOf(QualifiedName.fromDottedString(PyDataclassNames.Dataclasses.DATACLASSES_FIELD)),
|
||||
)
|
||||
}
|
||||
PyDataclassParameters.PredefinedType.ATTRS -> {
|
||||
// TODO remove this hack, make it a proper field
|
||||
val extraArguments = mutableMapOf<String, PyExpression>()
|
||||
if (type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS && stub.decoratorName() == KnownDecorator.ATTR_DATACLASS.qualifiedName) {
|
||||
extraArguments["auto_attribs"] =
|
||||
PyElementGenerator.getInstance(pyClass.project).createExpressionFromText(LanguageLevel.forElement(pyClass), PyNames.TRUE)
|
||||
}
|
||||
|
||||
return PyDataclassParameters(
|
||||
init = stub.initValue() ?: true,
|
||||
repr = stub.reprValue() ?: true,
|
||||
eq = stub.eqValue() ?: true,
|
||||
order = stub.orderValue() ?: true,
|
||||
unsafeHash = stub.unsafeHashValue() ?: false,
|
||||
frozen = stub.frozenValue() ?: (stub.decoratorName()?.toString() in PyDataclassNames.Attrs.ATTRS_FROZEN),
|
||||
kwOnly = stub.kwOnly() ?: false,
|
||||
initArgument = argumentMapping?.initArgument,
|
||||
reprArgument = argumentMapping?.reprArgument,
|
||||
eqArgument = argumentMapping?.eqArgument,
|
||||
orderArgument = argumentMapping?.orderArgument,
|
||||
unsafeHashArgument = argumentMapping?.unsafeHashArgument,
|
||||
frozenArgument = argumentMapping?.frozenArgument,
|
||||
kwOnlyArgument = argumentMapping?.kwOnlyArgument,
|
||||
others = (argumentMapping?.others ?: emptyMap()) + extraArguments,
|
||||
type = type,
|
||||
fieldSpecifiers = PyDataclassNames.Attrs.FIELD_FUNCTIONS.map(QualifiedName::fromDottedString),
|
||||
)
|
||||
}
|
||||
PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM -> {
|
||||
val dataclassTransformTargets = (pyClass.decoratorList?.decorators.orEmpty().asSequence().flatMap { resolveDecoratorStubSafe(it, context) }
|
||||
+ sequence { yieldAll(pyClass.getAncestorClasses(context)) }
|
||||
+ sequence { (pyClass.getMetaClassType(true, context) as? PyClassType)?.let { yield(it.pyClass) } })
|
||||
val dataclassTransformDecorator: PyDecorator? = dataclassTransformTargets
|
||||
.filterIsInstance<PyDecoratable>()
|
||||
.flatMap { it.decoratorList?.decorators.orEmpty().asSequence() }
|
||||
.filter { it.qualifiedName?.lastComponent == "dataclass_transform" }
|
||||
.firstOrNull()
|
||||
|
||||
if (dataclassTransformDecorator != null) {
|
||||
val dataclassTransformStub: PyDataclassTransformDecoratorStub? = StubAwareComputation.on(dataclassTransformDecorator)
|
||||
.withCustomStub { dtStub -> dtStub.getCustomStub(PyDataclassTransformDecoratorStub::class.java) }
|
||||
.overStub { dtStub -> dtStub }
|
||||
.withStubBuilder(PyDataclassTransformDecoratorStub::create)
|
||||
.compute(context)
|
||||
|
||||
|
||||
if (dataclassTransformStub != null) {
|
||||
val resolvedFieldSpecifiers = dataclassTransformStub.fieldSpecifiers
|
||||
.flatMap { PyResolveUtil.resolveQualifiedNameInScope(it, ScopeUtil.getScopeOwner(dataclassTransformDecorator)!!, context) }
|
||||
.filterIsInstance<PyQualifiedNameOwner>()
|
||||
.mapNotNull { it.qualifiedName }
|
||||
.map { QualifiedName.fromDottedString(it) }
|
||||
return PyDataclassParameters(
|
||||
init = stub.initValue() ?: true,
|
||||
repr = stub.reprValue() ?: true,
|
||||
eq = stub.eqValue() ?: dataclassTransformStub.eqDefault,
|
||||
order = stub.orderValue() ?: dataclassTransformStub.orderDefault,
|
||||
unsafeHash = stub.unsafeHashValue() ?: true,
|
||||
frozen = stub.frozenValue() ?: dataclassTransformStub.frozenDefault,
|
||||
kwOnly = stub.kwOnly() ?: dataclassTransformStub.kwOnlyDefault,
|
||||
initArgument = argumentMapping?.initArgument,
|
||||
reprArgument = argumentMapping?.reprArgument,
|
||||
eqArgument = argumentMapping?.eqArgument,
|
||||
orderArgument = argumentMapping?.orderArgument,
|
||||
unsafeHashArgument = argumentMapping?.unsafeHashArgument,
|
||||
frozenArgument = argumentMapping?.frozenArgument,
|
||||
kwOnlyArgument = argumentMapping?.kwOnlyArgument,
|
||||
others = argumentMapping?.others ?: emptyMap(),
|
||||
type = type,
|
||||
fieldSpecifiers = resolvedFieldSpecifiers,
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
else -> {
|
||||
// Non-standard dataclasses supported by third-party PyDataclassParametersProviders
|
||||
return PyDataclassParameters(
|
||||
init = stub.initValue() ?: true,
|
||||
repr = stub.reprValue() ?: true,
|
||||
eq = stub.eqValue() ?: true,
|
||||
order = stub.orderValue() ?: false,
|
||||
unsafeHash = stub.unsafeHashValue() ?: false,
|
||||
frozen = stub.frozenValue() ?: false,
|
||||
kwOnly = stub.kwOnly() ?: false,
|
||||
initArgument = argumentMapping?.initArgument,
|
||||
reprArgument = argumentMapping?.reprArgument,
|
||||
eqArgument = argumentMapping?.eqArgument,
|
||||
orderArgument = argumentMapping?.orderArgument,
|
||||
unsafeHashArgument = argumentMapping?.unsafeHashArgument,
|
||||
frozenArgument = argumentMapping?.frozenArgument,
|
||||
kwOnlyArgument = argumentMapping?.kwOnlyArgument,
|
||||
others = argumentMapping?.others ?: emptyMap(),
|
||||
type = type,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveDecoratorStubSafe(decorator: PyDecorator, context: TypeEvalContext): List<PsiElement> {
|
||||
val resolveContext = PyResolveContext.defaultContext(context)
|
||||
return StubAwareComputation.on(decorator)
|
||||
.overAst { psi -> psi.multiResolveCalleeFunction(resolveContext) as List<PsiElement> }
|
||||
.overStub { stub -> stub?.let { PyResolveUtil.resolveQualifiedNameInScope(it.qualifiedName, decorator.containingFile as ScopeOwner, context) } }
|
||||
.overAstStubLike { psi -> psi.qualifiedName?.let { PyResolveUtil.resolveQualifiedNameInScope(it, decorator.containingFile as ScopeOwner, context) } }
|
||||
.compute(context) ?: emptyList()
|
||||
}
|
||||
|
||||
data class PyDataclassFieldParameters(
|
||||
val hasDefault: Boolean,
|
||||
val hasDefaultFactory: Boolean,
|
||||
val initValue: Boolean,
|
||||
val kwOnly: Boolean,
|
||||
val alias: String?,
|
||||
)
|
||||
|
||||
fun resolveDataclassFieldParameters(
|
||||
dataclass: PyClass,
|
||||
dataclassParams: PyDataclassParameters,
|
||||
field: PyTargetExpression,
|
||||
context: TypeEvalContext,
|
||||
): PyDataclassFieldParameters? {
|
||||
assert(field.containingClass == dataclass)
|
||||
|
||||
val assignedQName = field.assignedQName
|
||||
if (assignedQName != null) {
|
||||
val resolvesToMissingOrNothing = PyResolveUtil.resolveQualifiedNameInScope(assignedQName, ScopeUtil.getScopeOwner(dataclass)!!, context)
|
||||
.filterIsInstance<PyQualifiedNameOwner>()
|
||||
.map { it.qualifiedName }
|
||||
.any { it == PyDataclassNames.Dataclasses.DATACLASSES_MISSING || it in PyDataclassNames.Attrs.ATTRS_NOTHING }
|
||||
if (resolvesToMissingOrNothing) {
|
||||
return PyDataclassFieldParameters(
|
||||
hasDefault = false,
|
||||
hasDefaultFactory = false,
|
||||
initValue = dataclassParams.init,
|
||||
kwOnly = dataclassParams.kwOnly,
|
||||
alias = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test that the following would cause unstubbing
|
||||
// val fieldStub = field.stub?.getCustomStub(PyDataclassFieldStub::class.java) ?: PyDataclassFieldStubImpl.create(field)
|
||||
val fieldStub = if (field.stub != null) {
|
||||
// TODO access the green stub here
|
||||
field.stub.getCustomStub(PyDataclassFieldStub::class.java)
|
||||
}
|
||||
else {
|
||||
PyDataclassFieldStubImpl.create(field)
|
||||
}
|
||||
if (dataclassParams.type.asPredefinedType != PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM) {
|
||||
return fieldStub?.let {
|
||||
PyDataclassFieldParameters(
|
||||
hasDefault = fieldStub.hasDefault(),
|
||||
hasDefaultFactory = fieldStub.hasDefaultFactory(),
|
||||
initValue = fieldStub.initValue(),
|
||||
kwOnly = fieldStub.kwOnly() ?: false,
|
||||
alias = fieldStub.alias,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (field.calleeName == null) return null
|
||||
val fieldSpecifierDeclaration = PyResolveUtil.resolveQualifiedNameInScope(field.calleeName!!, ScopeUtil.getScopeOwner(dataclass)!!, context)
|
||||
.mapNotNull {
|
||||
when (it) {
|
||||
is PyFunction -> it
|
||||
// TODO add a test for constructors
|
||||
is PyClass -> it.findInitOrNew(true, context)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
.firstOrNull {
|
||||
val qualifiedName = it.qualifiedName
|
||||
qualifiedName != null && QualifiedName.fromDottedString(qualifiedName) in dataclassParams.fieldSpecifiers
|
||||
}
|
||||
if (fieldSpecifierDeclaration == null) return null
|
||||
return PyDataclassFieldParameters(
|
||||
hasDefault = fieldStub?.hasDefault() ?: false,
|
||||
hasDefaultFactory = fieldStub?.hasDefaultFactory() ?: false,
|
||||
// TODO Should we delegate to dataclass parameters init here?
|
||||
// TODO support overloading init with Literal types
|
||||
initValue = fieldStub?.initValue() ?: getArgumentDefault("init", fieldSpecifierDeclaration) ?: true,
|
||||
kwOnly = fieldStub?.kwOnly() ?: getArgumentDefault("kw_only", fieldSpecifierDeclaration) ?: dataclassParams.kwOnly,
|
||||
alias = fieldStub?.alias,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getArgumentDefault(paramName: String, function: PyFunction): Boolean? {
|
||||
when (function.parameterList.findParameterByName(paramName)?.defaultValueText) {
|
||||
PyNames.TRUE -> return true
|
||||
PyNames.FALSE -> return false
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,17 +8,13 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.ast.PyAstFunction
|
||||
import com.jetbrains.python.codeInsight.*
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Attrs
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Dataclasses
|
||||
import com.jetbrains.python.codeInsight.PyDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.parseDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.parseStdDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionNavigator
|
||||
import com.jetbrains.python.psi.impl.stubs.PyDataclassFieldStubImpl
|
||||
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
|
||||
|
||||
@@ -130,6 +126,7 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
val parameters = parseDataclassParameters(current, context)
|
||||
|
||||
if (parameters == null) {
|
||||
// The base class decorated with @dataclass_transform gets filtered out already here, because for it we don't detect DataclassParameters
|
||||
if (PyKnownDecoratorUtil.hasUnknownDecorator(current, context)) break else continue
|
||||
}
|
||||
else if (parameters.type.asPredefinedType == null) {
|
||||
@@ -145,7 +142,7 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
.asReversed()
|
||||
.asSequence()
|
||||
.filterNot { PyTypingTypeProvider.isClassVar(it, context) }
|
||||
.mapNotNull { fieldToParameter(current, it, parameters.type, ellipsis, context) }
|
||||
.mapNotNull { fieldToParameter(current, it, parameters, ellipsis, context) }
|
||||
.filterNot { it.first in seenNames }
|
||||
.toList()
|
||||
|
||||
@@ -156,7 +153,9 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
fieldsInfo.forEachIndexed { index, (name, kwOnly, parameter) ->
|
||||
// note: attributes are visited from inheritors to ancestors, in reversed order for every of them
|
||||
|
||||
if ((seenKeywordOnlyClass || index < indexOfKeywordOnlyAttribute || kwOnly) && name !in collected) {
|
||||
if ((seenKeywordOnlyClass && (parameters.type == PyDataclassParameters.PredefinedType.ATTRS || kwOnly != false)
|
||||
|| index < indexOfKeywordOnlyAttribute || kwOnly == true)
|
||||
&& name !in collected) {
|
||||
keywordOnly += name
|
||||
}
|
||||
|
||||
@@ -164,7 +163,8 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
seenNames.add(name)
|
||||
}
|
||||
else if (!isKwOnlyMarkerField(parameter, context)) {
|
||||
if (parameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD) {
|
||||
if (parameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
parameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM) {
|
||||
// std: attribute that overrides ancestor's attribute does not change the order but updates type
|
||||
collected[name] = collected.remove(name) ?: parameter
|
||||
}
|
||||
@@ -208,21 +208,23 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
return positionalOrKeyword + listOf(PyCallableParameterImpl.psi(singleStarParameter)) + keyword
|
||||
}
|
||||
|
||||
private fun fieldToParameter(cls: PyClass,
|
||||
field: PyTargetExpression,
|
||||
dataclassType: PyDataclassParameters.Type,
|
||||
ellipsis: PyNoneLiteralExpression,
|
||||
context: TypeEvalContext): Triple<String, Boolean, PyCallableParameter?>? {
|
||||
private fun fieldToParameter(
|
||||
cls: PyClass,
|
||||
field: PyTargetExpression,
|
||||
dataclassParameters: PyDataclassParameters,
|
||||
ellipsis: PyNoneLiteralExpression,
|
||||
context: TypeEvalContext
|
||||
): Triple<String, Boolean?, PyCallableParameter?>? {
|
||||
val fieldName = field.name ?: return null
|
||||
|
||||
val stub = field.stub
|
||||
val fieldStub = if (stub == null) PyDataclassFieldStubImpl.create(field) else stub.getCustomStub(PyDataclassFieldStub::class.java)
|
||||
if (fieldStub != null && !fieldStub.initValue()) return Triple(fieldName, false, null)
|
||||
if (fieldStub == null && field.annotationValue == null) return null // skip fields that are not annotated
|
||||
val fieldParams = resolveDataclassFieldParameters(cls, dataclassParameters, field, context)
|
||||
if (fieldParams != null && !fieldParams.initValue) return Triple(fieldName, false, null)
|
||||
if (fieldParams == null && field.annotationValue == null) return null // skip fields that are not annotated
|
||||
|
||||
// TODO add support for name aliases
|
||||
val parameterName =
|
||||
fieldName.let {
|
||||
if (dataclassType.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS && PyUtil.getInitialUnderscores(it) == 1) {
|
||||
if (dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS && PyUtil.getInitialUnderscores(it) == 1) {
|
||||
it.substring(1)
|
||||
}
|
||||
else it
|
||||
@@ -230,12 +232,12 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
|
||||
val parameter = PyCallableParameterImpl.nonPsi(
|
||||
parameterName,
|
||||
getTypeForParameter(cls, field, dataclassType, context),
|
||||
getDefaultValueForParameter(cls, field, fieldStub, dataclassType, ellipsis, context),
|
||||
getTypeForParameter(cls, field, dataclassParameters.type, context),
|
||||
getDefaultValueForParameter(cls, field, fieldParams, dataclassParameters, ellipsis, context),
|
||||
field
|
||||
)
|
||||
|
||||
return Triple(parameterName, fieldStub?.kwOnly() == true, parameter)
|
||||
return Triple(parameterName, fieldParams?.kwOnly, parameter)
|
||||
}
|
||||
|
||||
private fun getTypeForParameter(cls: PyClass,
|
||||
@@ -263,22 +265,24 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
|
||||
return type
|
||||
}
|
||||
|
||||
private fun getDefaultValueForParameter(cls: PyClass,
|
||||
field: PyTargetExpression,
|
||||
fieldStub: PyDataclassFieldStub?,
|
||||
dataclassType: PyDataclassParameters.Type,
|
||||
ellipsis: PyNoneLiteralExpression,
|
||||
context: TypeEvalContext): PyExpression? {
|
||||
return if (fieldStub == null) {
|
||||
private fun getDefaultValueForParameter(
|
||||
cls: PyClass,
|
||||
field: PyTargetExpression,
|
||||
fieldParams: PyDataclassFieldParameters?,
|
||||
dataclassParams: PyDataclassParameters,
|
||||
ellipsis: PyNoneLiteralExpression,
|
||||
context: TypeEvalContext
|
||||
): PyExpression? {
|
||||
return if (fieldParams == null) {
|
||||
when {
|
||||
context.maySwitchToAST(field) -> field.findAssignedValue()
|
||||
field.hasAssignedValue() -> ellipsis
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else if (fieldStub.hasDefault() ||
|
||||
fieldStub.hasDefaultFactory() ||
|
||||
dataclassType.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS &&
|
||||
else if (fieldParams.hasDefault ||
|
||||
fieldParams.hasDefaultFactory ||
|
||||
dataclassParams.type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS &&
|
||||
methodDecoratedAsAttributeDefault(cls, field.name) != null) {
|
||||
ellipsis
|
||||
}
|
||||
|
||||
@@ -11,19 +11,14 @@ import com.intellij.psi.PsiNameIdentifierOwner
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.codeInsight.*
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Attrs
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Dataclasses
|
||||
import com.jetbrains.python.codeInsight.PyDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.parseDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.parseStdDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.resolvesToOmittedDefault
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.ParamHelper
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
import com.jetbrains.python.psi.impl.stubs.PyDataclassFieldStubImpl
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassFieldStub
|
||||
import com.jetbrains.python.psi.types.*
|
||||
import one.util.streamex.StreamEx
|
||||
|
||||
@@ -65,7 +60,8 @@ class PyDataclassInspection : PyInspection() {
|
||||
val dataclassParameters = parseDataclassParameters(node, myTypeEvalContext)
|
||||
|
||||
if (dataclassParameters != null) {
|
||||
if (dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD) {
|
||||
if (dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM) {
|
||||
processDataclassParameters(node, dataclassParameters)
|
||||
|
||||
val postInit = node.findMethodByName(Dataclasses.DUNDER_POST_INIT, false, myTypeEvalContext)
|
||||
@@ -78,7 +74,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
processAsInitVar(element, postInit)?.let { localInitVars.add(it) }
|
||||
}
|
||||
|
||||
processFieldFunctionCall(element)
|
||||
processFieldFunctionCall(node, dataclassParameters, element)
|
||||
}
|
||||
|
||||
true
|
||||
@@ -95,43 +91,42 @@ class PyDataclassInspection : PyInspection() {
|
||||
.findMethodByName(Attrs.DUNDER_POST_INIT, false, myTypeEvalContext)
|
||||
?.also { processAttrsPostInitDefinition(it, dataclassParameters) }
|
||||
|
||||
processAttrsDefaultThroughDecorator(node)
|
||||
processAttrsDefaultThroughDecorator(dataclassParameters, node)
|
||||
processAttrsInitializersAndValidators(node)
|
||||
processAttrIbFunctionCalls(node)
|
||||
processAttrIbFunctionCalls(dataclassParameters, node)
|
||||
}
|
||||
|
||||
processAnnotationsExistence(node, dataclassParameters)
|
||||
|
||||
PyNamedTupleInspection.inspectFieldsOrder(
|
||||
node,
|
||||
{
|
||||
cls = node,
|
||||
classFieldsFilter = {
|
||||
val parameters = parseDataclassParameters(it, myTypeEvalContext)
|
||||
parameters != null && !parameters.kwOnly
|
||||
},
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD,
|
||||
myTypeEvalContext,
|
||||
this::registerProblem,
|
||||
{
|
||||
val stub = it.stub
|
||||
val fieldStub = if (stub == null) PyDataclassFieldStubImpl.create(it)
|
||||
else stub.getCustomStub(PyDataclassFieldStub::class.java)
|
||||
checkInheritedOrder = (dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM),
|
||||
context = myTypeEvalContext,
|
||||
callback = this::registerProblem,
|
||||
fieldsFilter = {
|
||||
val dataclassParams = parseDataclassParameters(it.containingClass!!, myTypeEvalContext)!!
|
||||
val fieldParams = resolveDataclassFieldParameters(it.containingClass!!, dataclassParams, it, myTypeEvalContext)
|
||||
|
||||
(fieldStub == null || fieldStub.initValue() && !fieldStub.kwOnly()) &&
|
||||
!(fieldStub == null && it.annotationValue == null) && // skip fields that are not annotated
|
||||
!PyTypingTypeProvider.isClassVar(it, myTypeEvalContext) // skip classvars
|
||||
return@inspectFieldsOrder (fieldParams == null || fieldParams.initValue && !fieldParams.kwOnly) &&
|
||||
!(fieldParams == null && it.annotationValue == null) && // skip fields that are not annotated
|
||||
!PyTypingTypeProvider.isClassVar(it, myTypeEvalContext) // skip classvars
|
||||
},
|
||||
{
|
||||
val fieldStub = PyDataclassFieldStubImpl.create(it)
|
||||
|
||||
if (fieldStub != null) {
|
||||
fieldStub.hasDefault() ||
|
||||
fieldStub.hasDefaultFactory() ||
|
||||
hasAssignedValue = {
|
||||
val dataclassParams = parseDataclassParameters(it.containingClass!!, myTypeEvalContext)!!
|
||||
val fieldParams = resolveDataclassFieldParameters(it.containingClass!!, dataclassParams, it, myTypeEvalContext)
|
||||
if (fieldParams != null) {
|
||||
fieldParams.hasDefault ||
|
||||
fieldParams.hasDefaultFactory ||
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS &&
|
||||
node.methods.any { m -> m.decoratorList?.findDecorator("${it.name}.default") != null }
|
||||
}
|
||||
else {
|
||||
val assignedValue = it.findAssignedValue()
|
||||
assignedValue != null && !resolvesToOmittedDefault(assignedValue, dataclassParameters.type)
|
||||
it.hasAssignedValue()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -327,7 +322,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
|
||||
var frozenInHierarchy: Boolean? = null
|
||||
for (current in StreamEx.of(cls).append(cls.getAncestorClasses(myTypeEvalContext))) {
|
||||
val currentFrozen = parseStdDataclassParameters(current, myTypeEvalContext)?.frozen ?: continue
|
||||
val currentFrozen = parseStdOrDataclassTransformDataclassParameters(current, myTypeEvalContext)?.frozen ?: continue
|
||||
|
||||
if (frozenInHierarchy == null) {
|
||||
frozenInHierarchy = currentFrozen
|
||||
@@ -431,7 +426,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun processAttrsDefaultThroughDecorator(cls: PyClass) {
|
||||
private fun processAttrsDefaultThroughDecorator(dataclassParameters: PyDataclassParameters, cls: PyClass) {
|
||||
val initializers = mutableMapOf<String, MutableList<PyFunction>>()
|
||||
|
||||
cls.methods.forEach { method ->
|
||||
@@ -449,10 +444,10 @@ class PyDataclassInspection : PyInspection() {
|
||||
if (attribute != null) {
|
||||
initializers.computeIfAbsent(name, { mutableListOf() }).add(method)
|
||||
|
||||
val stub = PyDataclassFieldStubImpl.create(attribute)
|
||||
if (stub != null && (stub.hasDefault() || stub.hasDefaultFactory())) {
|
||||
val fieldParams = resolveDataclassFieldParameters(cls, dataclassParameters, attribute, myTypeEvalContext)
|
||||
if (fieldParams != null && (fieldParams.hasDefault || fieldParams.hasDefaultFactory)) {
|
||||
registerProblem(method.nameIdentifier,
|
||||
PyPsiBundle.message("INSP.dataclasses.attribute.default.set.using.method", "${stub.calleeName}()"),
|
||||
PyPsiBundle.message("INSP.dataclasses.attribute.default.set.using.method", "${attribute.calleeName}()"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
@@ -508,9 +503,12 @@ class PyDataclassInspection : PyInspection() {
|
||||
|
||||
private fun processAnnotationsExistence(cls: PyClass, dataclassParameters: PyDataclassParameters) {
|
||||
if (dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.STD ||
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM ||
|
||||
PyEvaluator.evaluateAsBoolean(PyUtil.peelArgument(dataclassParameters.others["auto_attribs"]), false)) {
|
||||
cls.processClassLevelDeclarations { element, _ ->
|
||||
if (element is PyTargetExpression && element.annotation == null && PyDataclassFieldStubImpl.create(element) != null) {
|
||||
if (element is PyTargetExpression
|
||||
&& element.annotation == null
|
||||
&& resolveDataclassFieldParameters(cls, dataclassParameters, element, myTypeEvalContext) != null) {
|
||||
registerProblem(element, PyPsiBundle.message("INSP.dataclasses.attribute.lacks.type.annotation", element.name),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
@@ -520,15 +518,15 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun processAttrIbFunctionCalls(cls: PyClass) {
|
||||
cls.processClassLevelDeclarations { element, _ ->
|
||||
private fun processAttrIbFunctionCalls(dataclassParameters: PyDataclassParameters, dataclass: PyClass) {
|
||||
dataclass.processClassLevelDeclarations { element, _ ->
|
||||
if (element is PyTargetExpression) {
|
||||
val fieldParams = resolveDataclassFieldParameters(dataclass, dataclassParameters, element, myTypeEvalContext)
|
||||
val call = element.findAssignedValue() as? PyCallExpression
|
||||
val stub = PyDataclassFieldStubImpl.create(element)
|
||||
|
||||
if (call != null && stub != null) {
|
||||
if (stub.hasDefaultFactory()) {
|
||||
if (stub.hasDefault()) {
|
||||
if (call != null && fieldParams != null) {
|
||||
if (fieldParams.hasDefaultFactory) {
|
||||
if (fieldParams.hasDefault) {
|
||||
registerProblem(call.argumentList, PyPsiBundle.message("INSP.dataclasses.cannot.specify.both.default.and.factory"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
@@ -565,18 +563,18 @@ class PyDataclassInspection : PyInspection() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processFieldFunctionCall(field: PyTargetExpression) {
|
||||
val fieldStub = PyDataclassFieldStubImpl.create(field) ?: return
|
||||
private fun processFieldFunctionCall(dataclass: PyClass, dataclassParameters: PyDataclassParameters, field: PyTargetExpression) {
|
||||
val fieldStub = resolveDataclassFieldParameters(dataclass, dataclassParameters, field, myTypeEvalContext) ?: return
|
||||
val call = field.findAssignedValue() as? PyCallExpression ?: return
|
||||
|
||||
if (PyTypingTypeProvider.isClassVar(field, myTypeEvalContext) || isInitVar(field)) {
|
||||
if (fieldStub.hasDefaultFactory()) {
|
||||
if (fieldStub.hasDefaultFactory) {
|
||||
registerProblem(call.getKeywordArgument("default_factory"),
|
||||
PyPsiBundle.message("INSP.dataclasses.field.cannot.have.default.factory"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
else if (fieldStub.hasDefault() && fieldStub.hasDefaultFactory()) {
|
||||
else if (fieldStub.hasDefault && fieldStub.hasDefaultFactory) {
|
||||
registerProblem(call.argumentList, PyPsiBundle.message("INSP.dataclasses.cannot.specify.both.default.and.default.factory"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class PyFileElementType extends IStubFileElementType<PyFileStub> {
|
||||
@Override
|
||||
public int getStubVersion() {
|
||||
// Don't forget to update versions of indexes that use the updated stub-based elements
|
||||
return 93;
|
||||
return 94;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -6,13 +6,16 @@ package com.jetbrains.python.psi.impl.stubs
|
||||
import com.intellij.psi.stubs.StubInputStream
|
||||
import com.intellij.psi.stubs.StubOutputStream
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.intellij.util.io.DataInputOutputUtil
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Attrs
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Dataclasses
|
||||
import com.jetbrains.python.codeInsight.PyDataclassParameters
|
||||
import com.jetbrains.python.codeInsight.resolvesToOmittedDefault
|
||||
import com.jetbrains.python.psi.PyCallExpression
|
||||
import com.jetbrains.python.psi.PyReferenceExpression
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression
|
||||
import com.jetbrains.python.psi.PyTargetExpression
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
@@ -20,16 +23,17 @@ import com.jetbrains.python.psi.stubs.PyDataclassFieldStub
|
||||
import java.io.IOException
|
||||
|
||||
class PyDataclassFieldStubImpl private constructor(private val calleeName: QualifiedName,
|
||||
private val parameters: FieldParameters) : PyDataclassFieldStub {
|
||||
private val hasDefault: Boolean,
|
||||
private val hasDefaultFactory: Boolean,
|
||||
private val initValue: Boolean,
|
||||
private val kwOnly: Boolean?,
|
||||
private val alias: String?,
|
||||
) : PyDataclassFieldStub {
|
||||
companion object {
|
||||
fun create(expression: PyTargetExpression): PyDataclassFieldStub? {
|
||||
val value = expression.findAssignedValue() as? PyCallExpression ?: return null
|
||||
val callee = value.callee as? PyReferenceExpression ?: return null
|
||||
|
||||
val calleeNameAndType = calculateCalleeNameAndType(callee) ?: return null
|
||||
val parameters = analyzeArguments(value, calleeNameAndType.second) ?: return null
|
||||
|
||||
return PyDataclassFieldStubImpl(calleeNameAndType.first, parameters)
|
||||
val callExpr = expression.findAssignedValue() as? PyCallExpression ?: return null
|
||||
val predefinedType = calculateCalleeNameAndType(callExpr) ?: return null
|
||||
return analyzeArguments(callExpr, predefinedType)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -38,55 +42,96 @@ class PyDataclassFieldStubImpl private constructor(private val calleeName: Quali
|
||||
val hasDefault = stream.readBoolean()
|
||||
val hasDefaultFactory = stream.readBoolean()
|
||||
val initValue = stream.readBoolean()
|
||||
val kwOnly = stream.readBoolean()
|
||||
val kwOnly = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val alias = stream.readNameString()
|
||||
|
||||
return PyDataclassFieldStubImpl(
|
||||
QualifiedName.fromDottedString(calleeName),
|
||||
FieldParameters(hasDefault, hasDefaultFactory, initValue, kwOnly)
|
||||
calleeName = QualifiedName.fromDottedString(calleeName),
|
||||
hasDefault = hasDefault,
|
||||
hasDefaultFactory = hasDefaultFactory,
|
||||
initValue = initValue,
|
||||
kwOnly = kwOnly,
|
||||
alias = alias,
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateCalleeNameAndType(callee: PyReferenceExpression): Pair<QualifiedName, PyDataclassParameters.PredefinedType>? {
|
||||
val qualifiedName = callee.asQualifiedName() ?: return null
|
||||
private fun calculateCalleeNameAndType(fieldInitializer: PyCallExpression): PyDataclassParameters.PredefinedType? {
|
||||
val callee = fieldInitializer.callee as? PyReferenceExpression ?: return null
|
||||
|
||||
for (originalQName in PyResolveUtil.resolveImportedElementQNameLocally(callee)) {
|
||||
when (originalQName.toString()) {
|
||||
Dataclasses.DATACLASSES_FIELD -> return qualifiedName to PyDataclassParameters.PredefinedType.STD
|
||||
in Attrs.FIELD_FUNCTIONS -> return qualifiedName to PyDataclassParameters.PredefinedType.ATTRS
|
||||
Dataclasses.DATACLASSES_FIELD -> return PyDataclassParameters.PredefinedType.STD
|
||||
in Attrs.FIELD_FUNCTIONS -> return PyDataclassParameters.PredefinedType.ATTRS
|
||||
}
|
||||
}
|
||||
|
||||
// Any function call with these keyword arguments is a potential dataclass field initializer
|
||||
if (PyDataclassNames.DataclassTransform.FIELD_SPECIFIER_PARAMETERS.any { fieldInitializer.getKeywordArgument(it) != null }) {
|
||||
return PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun analyzeArguments(call: PyCallExpression, type: PyDataclassParameters.PredefinedType): FieldParameters? {
|
||||
private fun analyzeArguments(call: PyCallExpression, type: PyDataclassParameters.PredefinedType): PyDataclassFieldStub? {
|
||||
val qualifiedName = (call.callee as? PyReferenceExpression)?.asQualifiedName() ?: return null
|
||||
val initValue = PyEvaluator.evaluateAsBooleanNoResolve(call.getKeywordArgument("init"), true)
|
||||
val kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(call.getKeywordArgument("kw_only"))
|
||||
val default = call.getKeywordArgument("default")
|
||||
val defaultFactory = call.getKeywordArgument("default_factory")
|
||||
val factory = call.getKeywordArgument("factory")
|
||||
val alias = (call.getKeywordArgument("alias") as? PyStringLiteralExpression)?.stringValue
|
||||
|
||||
if (type == PyDataclassParameters.PredefinedType.STD) {
|
||||
val default = call.getKeywordArgument("default")
|
||||
val defaultFactory = call.getKeywordArgument("default_factory")
|
||||
val kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(call.getKeywordArgument("kw_only"), false)
|
||||
|
||||
return FieldParameters(default != null && !resolvesToOmittedDefault(default, type),
|
||||
defaultFactory != null && !resolvesToOmittedDefault(defaultFactory, type),
|
||||
initValue,
|
||||
kwOnly)
|
||||
return PyDataclassFieldStubImpl(
|
||||
calleeName = qualifiedName,
|
||||
hasDefault = default != null && !resolvesToOmittedDefault(default, type),
|
||||
hasDefaultFactory = defaultFactory != null && !resolvesToOmittedDefault(defaultFactory, type),
|
||||
initValue = initValue,
|
||||
kwOnly = kwOnly,
|
||||
alias = null
|
||||
)
|
||||
}
|
||||
else if (type == PyDataclassParameters.PredefinedType.ATTRS) {
|
||||
val default = call.getKeywordArgument("default")
|
||||
val hasFactory = call.getKeywordArgument("factory").let { it != null && it.text != PyNames.NONE }
|
||||
val kwOnly = PyEvaluator.evaluateAsBooleanNoResolve(call.getKeywordArgument("kw_only"), false)
|
||||
|
||||
val hasFactory = factory.let { it != null && it.text != PyNames.NONE }
|
||||
|
||||
if (default != null && !resolvesToOmittedDefault(default, type)) {
|
||||
val callee = (default as? PyCallExpression)?.callee as? PyReferenceExpression
|
||||
val hasFactoryInDefault =
|
||||
callee != null &&
|
||||
PyResolveUtil.resolveImportedElementQNameLocally(callee).any { it.toString() in Attrs.ATTRS_FACTORY }
|
||||
|
||||
return FieldParameters(!hasFactoryInDefault, hasFactory || hasFactoryInDefault, initValue, kwOnly)
|
||||
return PyDataclassFieldStubImpl(
|
||||
calleeName = qualifiedName,
|
||||
hasDefault = !hasFactoryInDefault,
|
||||
hasDefaultFactory = hasFactory || hasFactoryInDefault,
|
||||
initValue = initValue,
|
||||
kwOnly = kwOnly,
|
||||
alias = alias
|
||||
)
|
||||
}
|
||||
|
||||
return FieldParameters(false, hasFactory, initValue, kwOnly)
|
||||
return PyDataclassFieldStubImpl(
|
||||
calleeName = qualifiedName,
|
||||
hasDefault = false,
|
||||
hasDefaultFactory = hasFactory,
|
||||
initValue = initValue,
|
||||
kwOnly = kwOnly,
|
||||
alias = alias
|
||||
)
|
||||
}
|
||||
else if (type == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM) {
|
||||
return PyDataclassFieldStubImpl(
|
||||
calleeName = qualifiedName,
|
||||
// dataclasses.MISSING is not mentioned in the spec, but because dataclasses.KW_ONLY is supported,
|
||||
// this one is special-cases as well
|
||||
hasDefault = default != null && !resolvesToOmittedDefault(default, type),
|
||||
hasDefaultFactory = defaultFactory != null && !resolvesToOmittedDefault(defaultFactory, type) ||
|
||||
factory != null && !resolvesToOmittedDefault(factory, type),
|
||||
initValue = initValue, // TODO How should we handle custom field specifiers where init=False by default
|
||||
kwOnly = kwOnly,
|
||||
alias = alias
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -99,29 +144,28 @@ class PyDataclassFieldStubImpl private constructor(private val calleeName: Quali
|
||||
|
||||
override fun serialize(stream: StubOutputStream) {
|
||||
stream.writeName(calleeName.toString())
|
||||
stream.writeBoolean(parameters.hasDefault)
|
||||
stream.writeBoolean(parameters.hasDefaultFactory)
|
||||
stream.writeBoolean(parameters.initValue)
|
||||
stream.writeBoolean(parameters.kwOnly)
|
||||
stream.writeBoolean(hasDefault)
|
||||
stream.writeBoolean(hasDefaultFactory)
|
||||
stream.writeBoolean(initValue)
|
||||
DataInputOutputUtil.writeNullable(stream, kwOnly, stream::writeBoolean)
|
||||
stream.writeName(alias)
|
||||
}
|
||||
|
||||
override fun getCalleeName(): QualifiedName = calleeName
|
||||
override fun hasDefault(): Boolean = parameters.hasDefault
|
||||
override fun hasDefaultFactory(): Boolean = parameters.hasDefaultFactory
|
||||
override fun initValue(): Boolean = parameters.initValue
|
||||
override fun kwOnly(): Boolean = parameters.kwOnly
|
||||
override fun hasDefault(): Boolean = hasDefault
|
||||
override fun hasDefaultFactory(): Boolean = hasDefaultFactory
|
||||
override fun initValue(): Boolean = initValue
|
||||
override fun kwOnly(): Boolean? = kwOnly
|
||||
override fun getAlias(): String? = alias
|
||||
|
||||
override fun toString(): String {
|
||||
return "PyDataclassFieldStubType(callee=$calleeName" +
|
||||
", hasDefault=${parameters.hasDefault}" +
|
||||
", hasDefaultFactory=${parameters.hasDefaultFactory}" +
|
||||
", initValue=${parameters.initValue}" +
|
||||
", kwOnly=${parameters.kwOnly}" +
|
||||
", hasDefault=${hasDefault}" +
|
||||
", hasDefaultFactory=${hasDefaultFactory}" +
|
||||
", initValue=${initValue}" +
|
||||
", kwOnly=${kwOnly}" +
|
||||
", alias=${alias}" +
|
||||
")"
|
||||
}
|
||||
|
||||
private data class FieldParameters(val hasDefault: Boolean,
|
||||
val hasDefaultFactory: Boolean,
|
||||
val initValue: Boolean,
|
||||
val kwOnly: Boolean)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.jetbrains.python.psi.impl.stubs
|
||||
|
||||
import com.intellij.psi.stubs.StubInputStream
|
||||
import com.intellij.psi.stubs.StubOutputStream
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.intellij.util.io.DataInputOutputUtil
|
||||
import com.jetbrains.python.codeInsight.PyDataclassParameters.PredefinedType
|
||||
import com.jetbrains.python.codeInsight.parseDataclassParametersForStub
|
||||
import com.jetbrains.python.psi.PyClass
|
||||
import com.jetbrains.python.psi.stubs.PyDataclassStub
|
||||
@@ -20,36 +23,48 @@ class PyDataclassStubType : PyCustomClassStubType<PyDataclassStub>() {
|
||||
}
|
||||
}
|
||||
|
||||
class PyDataclassStubImpl private constructor(private val type: String,
|
||||
private val init: Boolean,
|
||||
private val repr: Boolean,
|
||||
private val eq: Boolean,
|
||||
private val order: Boolean,
|
||||
private val unsafeHash: Boolean,
|
||||
private val frozen: Boolean,
|
||||
private val kwOnly: Boolean) : PyDataclassStub {
|
||||
class PyDataclassStubImpl(
|
||||
private val type: String,
|
||||
private val decoratorName: QualifiedName?,
|
||||
private val init: Boolean?,
|
||||
private val repr: Boolean?,
|
||||
private val eq: Boolean?,
|
||||
private val order: Boolean?,
|
||||
private val unsafeHash: Boolean?,
|
||||
private val frozen: Boolean?,
|
||||
private val kwOnly: Boolean?,
|
||||
) : PyDataclassStub {
|
||||
|
||||
companion object {
|
||||
val NON_PARAMETERIZED_DATACLASS_TRANSFORM_CANDIDATE_STUB = PyDataclassStubImpl(
|
||||
type = PredefinedType.DATACLASS_TRANSFORM.name,
|
||||
decoratorName = null,
|
||||
init = null,
|
||||
repr = null,
|
||||
eq = null,
|
||||
order = null,
|
||||
unsafeHash = null,
|
||||
frozen = null,
|
||||
kwOnly = null
|
||||
)
|
||||
|
||||
fun create(cls: PyClass): PyDataclassStub? {
|
||||
return parseDataclassParametersForStub(cls)?.let {
|
||||
PyDataclassStubImpl(it.type.toString(), it.init, it.repr, it.eq, it.order, it.unsafeHash, it.frozen, it.kwOnly)
|
||||
}
|
||||
return parseDataclassParametersForStub(cls)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun deserialize(stream: StubInputStream): PyDataclassStub? {
|
||||
val type = stream.readNameString() ?: return null
|
||||
val decoratorName = QualifiedName.deserialize(stream)
|
||||
val init = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val repr = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val eq = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val order = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val unsafeHash = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val frozen = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
val kwOnly = DataInputOutputUtil.readNullable(stream, stream::readBoolean)
|
||||
|
||||
val init = stream.readBoolean()
|
||||
val repr = stream.readBoolean()
|
||||
val eq = stream.readBoolean()
|
||||
val order = stream.readBoolean()
|
||||
val unsafeHash = stream.readBoolean()
|
||||
val frozen = stream.readBoolean()
|
||||
val kwOnly = stream.readBoolean()
|
||||
|
||||
return PyDataclassStubImpl(type, init, repr, eq, order, unsafeHash, frozen, kwOnly)
|
||||
return PyDataclassStubImpl(type, decoratorName, init, repr, eq, order, unsafeHash, frozen, kwOnly)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,21 +72,23 @@ class PyDataclassStubImpl private constructor(private val type: String,
|
||||
|
||||
override fun serialize(stream: StubOutputStream) {
|
||||
stream.writeName(type)
|
||||
stream.writeBoolean(init)
|
||||
stream.writeBoolean(repr)
|
||||
stream.writeBoolean(eq)
|
||||
stream.writeBoolean(order)
|
||||
stream.writeBoolean(unsafeHash)
|
||||
stream.writeBoolean(frozen)
|
||||
stream.writeBoolean(kwOnly)
|
||||
QualifiedName.serialize(decoratorName, stream)
|
||||
DataInputOutputUtil.writeNullable(stream, init, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, repr, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, eq, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, order, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, unsafeHash, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, frozen, stream::writeBoolean)
|
||||
DataInputOutputUtil.writeNullable(stream, kwOnly, stream::writeBoolean)
|
||||
}
|
||||
|
||||
override fun getType(): String = type
|
||||
override fun initValue(): Boolean = init
|
||||
override fun reprValue(): Boolean = repr
|
||||
override fun eqValue(): Boolean = eq
|
||||
override fun orderValue(): Boolean = order
|
||||
override fun unsafeHashValue(): Boolean = unsafeHash
|
||||
override fun frozenValue(): Boolean = frozen
|
||||
override fun kwOnly(): Boolean = kwOnly
|
||||
override fun decoratorName(): QualifiedName? = decoratorName
|
||||
override fun initValue(): Boolean? = init
|
||||
override fun reprValue(): Boolean? = repr
|
||||
override fun eqValue(): Boolean? = eq
|
||||
override fun orderValue(): Boolean? = order
|
||||
override fun unsafeHashValue(): Boolean? = unsafeHash
|
||||
override fun frozenValue(): Boolean? = frozen
|
||||
override fun kwOnly(): Boolean? = kwOnly
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package com.jetbrains.python.psi.stubs;
|
||||
|
||||
import com.jetbrains.python.psi.impl.stubs.CustomTargetExpressionStub;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface PyDataclassFieldStub extends CustomTargetExpressionStub {
|
||||
|
||||
@@ -23,7 +24,13 @@ public interface PyDataclassFieldStub extends CustomTargetExpressionStub {
|
||||
boolean initValue();
|
||||
|
||||
/**
|
||||
* @return true if field is used in `__init__` as a keyword-only parameter (attrs).
|
||||
* Whether the corresponding field should be used in `__init__` as a keyword-only parameter.
|
||||
* <p>
|
||||
* When {@code null}, this property of the field depends on the value of {@code kw_only} argument of
|
||||
* a {@code @dataclass_transform}-powered decorator or a base class and {@code kw_only_default} parameter default
|
||||
* of {@code @dataclass_transform} itself.
|
||||
*/
|
||||
boolean kwOnly();
|
||||
@Nullable Boolean kwOnly();
|
||||
|
||||
@Nullable String getAlias();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi.stubs;
|
||||
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.impl.stubs.PyCustomClassStub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface PyDataclassStub extends PyCustomClassStub {
|
||||
|
||||
@@ -12,45 +14,47 @@ public interface PyDataclassStub extends PyCustomClassStub {
|
||||
@NotNull
|
||||
String getType();
|
||||
|
||||
@Nullable QualifiedName decoratorName();
|
||||
|
||||
/**
|
||||
* @return value of `init` parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean initValue();
|
||||
@Nullable Boolean initValue();
|
||||
|
||||
/**
|
||||
* @return value of `repr` parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean reprValue();
|
||||
@Nullable Boolean reprValue();
|
||||
|
||||
/**
|
||||
* @return value of `eq` (std) or `cmp` (attrs) parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean eqValue();
|
||||
@Nullable Boolean eqValue();
|
||||
|
||||
/**
|
||||
* @return value of `order` (std) or `cmp` (attrs) parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean orderValue();
|
||||
@Nullable Boolean orderValue();
|
||||
|
||||
/**
|
||||
* @return value of `unsafe_hash` (std) or `hash` (attrs) parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean unsafeHashValue();
|
||||
@Nullable Boolean unsafeHashValue();
|
||||
|
||||
/**
|
||||
* @return value of `frozen` parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean frozenValue();
|
||||
@Nullable Boolean frozenValue();
|
||||
|
||||
/**
|
||||
* @return value of `kw_only` (attrs) parameter or
|
||||
* its default value if it is not specified or could not be evaluated.
|
||||
*/
|
||||
boolean kwOnly();
|
||||
@Nullable Boolean kwOnly();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.jetbrains.python.psi.stubs
|
||||
|
||||
import com.intellij.psi.stubs.StubInputStream
|
||||
import com.intellij.psi.stubs.StubOutputStream
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.intellij.util.io.DataInputOutputUtil
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
import com.jetbrains.python.psi.impl.stubs.PyCustomDecoratorStub
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Represents arguments of `typing.dataclass_transform` or `typing_extensions.dataclass_transform` decorators
|
||||
* applied to another decorator function or a base class for creating new dataclasses.
|
||||
*
|
||||
* Omitted arguments are expected to be replaced by their default values by the caller creating a new instance.
|
||||
*
|
||||
* See https://docs.python.org/3.13/library/typing.html#typing.dataclass_transform.
|
||||
*/
|
||||
data class PyDataclassTransformDecoratorStub(
|
||||
val eqDefault: Boolean,
|
||||
val orderDefault: Boolean,
|
||||
val kwOnlyDefault: Boolean,
|
||||
val frozenDefault: Boolean,
|
||||
val fieldSpecifiers: List<QualifiedName>,
|
||||
) : PyCustomDecoratorStub {
|
||||
|
||||
override fun getTypeClass(): Class<PyDataclassTransformDecoratorStubType> {
|
||||
return PyDataclassTransformDecoratorStubType::class.java
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun serialize(stream: StubOutputStream) {
|
||||
stream.writeBoolean(eqDefault)
|
||||
stream.writeBoolean(orderDefault)
|
||||
stream.writeBoolean(kwOnlyDefault)
|
||||
stream.writeBoolean(frozenDefault)
|
||||
DataInputOutputUtil.writeSeq(stream, fieldSpecifiers) { QualifiedName.serialize(it, stream) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun deserialize(stream: StubInputStream): PyDataclassTransformDecoratorStub {
|
||||
val eqDefault = stream.readBoolean()
|
||||
val orderDefault = stream.readBoolean()
|
||||
val kwOnlyDefault = stream.readBoolean()
|
||||
val frozenDefault = stream.readBoolean()
|
||||
val fieldSpecifiers = DataInputOutputUtil.readSeq(stream) { QualifiedName.deserialize(stream)!! }
|
||||
|
||||
return PyDataclassTransformDecoratorStub(
|
||||
eqDefault = eqDefault,
|
||||
orderDefault = orderDefault,
|
||||
kwOnlyDefault = kwOnlyDefault,
|
||||
frozenDefault = frozenDefault,
|
||||
fieldSpecifiers = fieldSpecifiers,
|
||||
)
|
||||
}
|
||||
|
||||
fun create(decorator: PyDecorator): PyDataclassTransformDecoratorStub? {
|
||||
val decoratorName = decorator.callee as? PyReferenceExpression ?: return null
|
||||
val importedAsDataclassTransform = PyResolveUtil.resolveImportedElementQNameLocally(decoratorName)
|
||||
.any { it.toString() in PyDataclassNames.DataclassTransform.DATACLASS_TRANSFORM_NAMES }
|
||||
if (importedAsDataclassTransform) {
|
||||
val fieldSpecifierList = PyUtil.peelArgument(decorator.getKeywordArgument("field_specifiers")) as? PyTupleExpression
|
||||
return PyDataclassTransformDecoratorStub(
|
||||
eqDefault = PyEvaluator.evaluateAsBooleanNoResolve(decorator.getKeywordArgument("eq_default"), true),
|
||||
orderDefault = PyEvaluator.evaluateAsBooleanNoResolve(decorator.getKeywordArgument("order_default"), false),
|
||||
kwOnlyDefault = PyEvaluator.evaluateAsBooleanNoResolve(decorator.getKeywordArgument("kw_only_default"), false),
|
||||
frozenDefault = PyEvaluator.evaluateAsBooleanNoResolve(decorator.getKeywordArgument("frozen_default"), false),
|
||||
fieldSpecifiers = fieldSpecifierList?.elements?.mapNotNull { (it as? PyReferenceExpression)?.asQualifiedName() } ?: emptyList(),
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.jetbrains.python.psi.stubs
|
||||
|
||||
import com.intellij.psi.stubs.StubInputStream
|
||||
import com.jetbrains.python.psi.PyDecorator
|
||||
import com.jetbrains.python.psi.impl.stubs.PyCustomDecoratorStubType
|
||||
import java.io.IOException
|
||||
|
||||
class PyDataclassTransformDecoratorStubType : PyCustomDecoratorStubType<PyDataclassTransformDecoratorStub> {
|
||||
override fun createStub(decorator: PyDecorator): PyDataclassTransformDecoratorStub? = PyDataclassTransformDecoratorStub.create(decorator)
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun deserializeStub(stream: StubInputStream): PyDataclassTransformDecoratorStub = PyDataclassTransformDecoratorStub.deserialize(stream)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
from dt_class import RecordViaBaseClass
|
||||
|
||||
RecordViaBaseClass(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>
|
||||
@@ -0,0 +1,8 @@
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class DataclassBase:
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
import dataclasses
|
||||
cls.__dir__ = dataclasses.dataclass(**kwargs)(cls).__dir__
|
||||
@@ -0,0 +1,6 @@
|
||||
from dt_base import DataclassBase
|
||||
|
||||
|
||||
class RecordViaBaseClass(DataclassBase, kw_only=True):
|
||||
id: int
|
||||
name: str
|
||||
@@ -0,0 +1,3 @@
|
||||
from dt_class import RecordViaDecorator
|
||||
|
||||
RecordViaDecorator(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>
|
||||
@@ -0,0 +1,7 @@
|
||||
from dt_decorator import my_dt_decorator
|
||||
|
||||
|
||||
@my_dt_decorator(kw_only=True)
|
||||
class RecordViaDecorator:
|
||||
id: int
|
||||
name: str
|
||||
@@ -0,0 +1,7 @@
|
||||
from typing import dataclass_transform, Callable
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dt_decorator(**kwargs) -> Callable[[type], type]:
|
||||
import dataclasses
|
||||
return dataclasses.dataclass(**kwargs) # type: ignore
|
||||
@@ -0,0 +1,3 @@
|
||||
from dt_class import RecordViaMetaClass
|
||||
|
||||
RecordViaMetaClass(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>
|
||||
@@ -0,0 +1,5 @@
|
||||
from dt_meta import DataclassMeta
|
||||
|
||||
|
||||
class DataclassBase(metaclass=DataclassMeta):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
from dt_base import DataclassBase
|
||||
|
||||
|
||||
class RecordViaMetaClass(DataclassBase, kw_only=True):
|
||||
id: int
|
||||
name: str
|
||||
@@ -0,0 +1,17 @@
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class DataclassMeta(type):
|
||||
def __new__(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
namespace,
|
||||
**kwargs,
|
||||
):
|
||||
import dataclasses
|
||||
return dataclasses.dataclass(**kwargs)(super().__new__(cls, name, bases, namespace)) # type: ignore
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from decorator import my_dataclass
|
||||
|
||||
|
||||
@my_dataclass(order=False)
|
||||
class Test1:
|
||||
def __gt__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class Test2:
|
||||
def __gt__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
print(Test1() < Test1())
|
||||
print(Test2() < Test2())
|
||||
|
||||
print(Test1() > Test1())
|
||||
print(Test2() > Test2())
|
||||
|
||||
print(Test1 < Test1)
|
||||
print(Test2 < Test2)
|
||||
|
||||
print(Test1 > Test1)
|
||||
print(Test2 > Test2)
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,23 @@
|
||||
from decorator import my_dataclass_order_default, my_dataclass
|
||||
|
||||
|
||||
@my_dataclass_order_default()
|
||||
class A1:
|
||||
x: int = 0
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A2:
|
||||
y: int = 0
|
||||
|
||||
|
||||
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
|
||||
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
|
||||
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
|
||||
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
|
||||
|
||||
|
||||
print(A1 < A2)
|
||||
print(A1 <= A2)
|
||||
print(A1 > A2)
|
||||
print(A1 >= A2)
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,35 @@
|
||||
from decorator import my_dataclass_order_default, my_dataclass
|
||||
|
||||
|
||||
@my_dataclass(order=True)
|
||||
class A1:
|
||||
x: int = 0
|
||||
|
||||
|
||||
@my_dataclass_order_default()
|
||||
class A2:
|
||||
y: int = 0
|
||||
|
||||
|
||||
print(A1(1) < A1(2))
|
||||
print(A1(1) <= A1(2))
|
||||
print(A1(1) > A1(2))
|
||||
print(A1(1) >= A1(2))
|
||||
|
||||
|
||||
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
|
||||
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
|
||||
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
|
||||
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
|
||||
|
||||
|
||||
print(A1 < A1)
|
||||
print(A1 <= A1)
|
||||
print(A1 > A1)
|
||||
print(A1 >= A1)
|
||||
|
||||
|
||||
print(A1 < A2)
|
||||
print(A1 <= A2)
|
||||
print(A1 > A2)
|
||||
print(A1 >= A2)
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,35 @@
|
||||
from decorator import my_dataclass_order_default, my_dataclass
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A1:
|
||||
x: int = 0
|
||||
|
||||
|
||||
@my_dataclass_order_default(order=False)
|
||||
class A2:
|
||||
y: int = 0
|
||||
|
||||
|
||||
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1'"><</error> A1(2))
|
||||
print(A1(1) <error descr="'__le__' not supported between instances of 'A1'"><=</error> A1(2))
|
||||
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1'">></error> A1(2))
|
||||
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1'">>=</error> A1(2))
|
||||
|
||||
|
||||
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
|
||||
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
|
||||
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
|
||||
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
|
||||
|
||||
|
||||
print(A1 < A1)
|
||||
print(A1 <= A1)
|
||||
print(A1 > A1)
|
||||
print(A1 >= A1)
|
||||
|
||||
|
||||
print(A1 < A2)
|
||||
print(A1 <= A2)
|
||||
print(A1 > A2)
|
||||
print(A1 >= A2)
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,8 @@
|
||||
import dataclasses
|
||||
|
||||
from mod import Base
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class <error descr="Non-default argument(s) follows default argument(s) defined in 'Base'">Sub</error>(Base):
|
||||
field_no_default: int
|
||||
@@ -0,0 +1,7 @@
|
||||
import dataclasses
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Base:
|
||||
base_field_no_default: int = dataclasses.MISSING
|
||||
base_field_default: int = 42
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from decorator import my_field, my_dataclass
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class E1:
|
||||
a: int = my_field(default=1)
|
||||
b1: int = my_field(default_factory=int)
|
||||
b2: int = my_field(factory=int)
|
||||
c1: int = my_field<error descr="Cannot specify both 'default' and 'default_factory'">(default=1, default_factory=int)</error>
|
||||
c2: int = my_field<error descr="Cannot specify both 'default' and 'default_factory'">(default=1, factory=int)</error>
|
||||
d: ClassVar[int] = my_field(default_factory=<error descr="Field cannot have a default factory">int</error>)
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def my_field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,8 @@
|
||||
from decorator import my_dataclass, my_field
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A1:
|
||||
<error descr="Attribute 'a' lacks a type annotation">a</error> = my_field()
|
||||
b = 1
|
||||
c: int = 1
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def my_field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,20 @@
|
||||
from bases import A1, A3, A41, A42
|
||||
from decorator import my_dataclass
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class <error descr="Non-default argument(s) follows default argument(s) defined in 'A1'">B1</error>(A1):
|
||||
y1: str
|
||||
y2: str = "1"
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B3(A3):
|
||||
y1: str
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B4<error descr="Inherited non-default argument(s) defined in A41 follows inherited default argument defined in A42">(A41, A42)</error>:
|
||||
pass
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from decorator import my_dataclass
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A1:
|
||||
x1: int
|
||||
x2: int = 1
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A3:
|
||||
x1: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A41:
|
||||
field1: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A42:
|
||||
field2: str = "1"
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def my_field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,93 @@
|
||||
import dataclasses
|
||||
from typing import ClassVar
|
||||
|
||||
from decorator import my_dataclass, my_field
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A1:
|
||||
bar1: int
|
||||
<error descr="Fields with a default value must come after any fields without a default.">baz1</error>: int = 1
|
||||
foo1: int
|
||||
<error descr="Fields with a default value must come after any fields without a default.">bar2</error>: int = 2
|
||||
baz2: int
|
||||
foo2: int = 3
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A2:
|
||||
bar: int
|
||||
baz: str = ""
|
||||
foo: int = 5
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A3:
|
||||
bar1: int
|
||||
baz1: ClassVar[int] = 1
|
||||
foo1: int
|
||||
bar2: ClassVar[int] = 2
|
||||
baz2: int
|
||||
foo2: int = 3
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A4:
|
||||
bar1: int
|
||||
baz1: ClassVar = 1
|
||||
foo1: int
|
||||
bar2: ClassVar = 2
|
||||
baz2: int
|
||||
foo2: int = 3
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B1:
|
||||
a: int = my_field()
|
||||
b: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B2:
|
||||
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = my_field(default=1)
|
||||
b: int = my_field()
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B3:
|
||||
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = my_field(default_factory=int)
|
||||
b: int = my_field()
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C1:
|
||||
x: int = dataclasses.MISSING
|
||||
y: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C2:
|
||||
x: int = my_field(default=dataclasses.MISSING)
|
||||
y: int
|
||||
|
||||
C2(1, 2)
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C3:
|
||||
x: int = my_field(default_factory=dataclasses.MISSING)
|
||||
y: int
|
||||
|
||||
C3(1, 2)
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class D1:
|
||||
x: int = 0
|
||||
y: int = my_field(init=False)
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class E1:
|
||||
foo = "bar" # <- has no type annotation, so doesn't count.
|
||||
baz: str
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def my_field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,75 @@
|
||||
from decorator import my_dataclass, my_dataclass_kw_only_default, my_field, my_filed_kw_only_default
|
||||
|
||||
|
||||
@my_dataclass(kw_only=True)
|
||||
class KwOnlyExplicitClassParam:
|
||||
kw_only_default: int = 42
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C1(KwOnlyExplicitClassParam):
|
||||
not_kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass_kw_only_default()
|
||||
class KwOnlyImplicitClassParam:
|
||||
kw_only_default: int = 42
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C2(KwOnlyImplicitClassParam):
|
||||
not_kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class KwOnlyExplicitFieldParam:
|
||||
kw_only_default: int = my_field(default=42, kw_only=True)
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C3(KwOnlyExplicitFieldParam):
|
||||
not_kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class KwOnlyImplicitFieldParam:
|
||||
kw_only_default: int = my_filed_kw_only_default(default=42)
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class C3(KwOnlyImplicitFieldParam):
|
||||
not_kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class BaseNotKwOnlyDefault:
|
||||
not_kw_only_default: int = 42
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class <error descr="Non-default argument(s) follows default argument(s) defined in 'BaseNotKwOnlyDefault'">SubNotKwOnly</error>(BaseNotKwOnlyDefault):
|
||||
not_kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass(kw_only=True)
|
||||
class SubKwOnlyExplicitClassParam(BaseNotKwOnlyDefault):
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass_kw_only_default()
|
||||
class SubKwOnlyImplicitClassParam(BaseNotKwOnlyDefault):
|
||||
kw_only_no_default: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class SubKwOnlyExplicitFieldParam(BaseNotKwOnlyDefault):
|
||||
kw_only_no_default: int = my_field(kw_only=True)
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class SubKwOnlyExplicitFieldParam(BaseNotKwOnlyDefault):
|
||||
kw_only_no_default: int = my_filed_kw_only_default()
|
||||
@@ -0,0 +1,22 @@
|
||||
import dataclasses
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def my_field(*, default=dataclasses.MISSING, kw_only=False):
|
||||
...
|
||||
|
||||
|
||||
def my_filed_kw_only_default(*, default=dataclasses.MISSING, kw_only=True):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field, my_filed_kw_only_default))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(kw_only_default=True, field_specifiers=(my_field, my_filed_kw_only_default))
|
||||
def my_dataclass_kw_only_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,84 @@
|
||||
from decorator import my_dataclass, my_dataclass_order_default, my_dataclass_frozen_default, my_dataclass_eq_default
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class ImplicitInit:
|
||||
field: int
|
||||
|
||||
def __init__(self, field):
|
||||
...
|
||||
|
||||
|
||||
@my_dataclass(<warning descr="'init' is ignored if the class already defines '__init__' method">init=True</warning>)
|
||||
class ExplicitInit:
|
||||
field: int
|
||||
|
||||
def __init__(self, field):
|
||||
...
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class ImplicitEq:
|
||||
field: int
|
||||
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
# Unclear semantic
|
||||
@my_dataclass_eq_default()
|
||||
class ImplicitEq2:
|
||||
field: int
|
||||
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
@my_dataclass(<warning descr="'eq' is ignored if the class already defines '__eq__' method">eq=True</warning>)
|
||||
class ExplicitEq:
|
||||
field: int
|
||||
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
# Unclear semantic
|
||||
@my_dataclass_order_default()
|
||||
class ImplicitOrder:
|
||||
field: int
|
||||
|
||||
def __gt__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
@my_dataclass(<error descr="'order' should be False if the class defines one of order methods">order=True</error>)
|
||||
class ExplicitOrder:
|
||||
field: int
|
||||
|
||||
def __gt__(self, other):
|
||||
pass
|
||||
|
||||
|
||||
@my_dataclass(<error descr="'unsafe_hash' should be False if the class defines '__hash__'">unsafe_hash=True</error>)
|
||||
class ExplicitUnsafeHash:
|
||||
field: int
|
||||
|
||||
def __hash__(self):
|
||||
pass
|
||||
|
||||
|
||||
# Unclear semantic
|
||||
@my_dataclass_frozen_default()
|
||||
class ImplicitFrozen:
|
||||
field: int
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
pass
|
||||
|
||||
|
||||
@my_dataclass(<error descr="'frozen' should be False if the class defines '__setattr__' or '__delattr__'">frozen=True</error>)
|
||||
class ExplicitFrozen:
|
||||
field: int
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
pass
|
||||
@@ -0,0 +1,23 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(eq_default=True)
|
||||
def my_dataclass_eq_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,8 @@
|
||||
import dataclasses
|
||||
|
||||
from mod import Base
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class <error descr="Non-default argument(s) follows default argument(s) defined in 'Base'">Sub</error>(Base):
|
||||
field_no_default: int
|
||||
@@ -0,0 +1,6 @@
|
||||
import dataclasses
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Base:
|
||||
field_default: int = 42
|
||||
@@ -0,0 +1,22 @@
|
||||
from bases import BaseFrozenExplicit, BaseNonFrozenImplicit, BaseFrozenWithFrozenDefault
|
||||
from decorator import my_dataclass, my_dataclass_frozen_default
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B1</error>(BaseFrozenExplicit):
|
||||
b: str = "2"
|
||||
|
||||
|
||||
@my_dataclass(<error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">frozen=True</error>)
|
||||
class B2(BaseNonFrozenImplicit):
|
||||
b: str = "2"
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B1</error>(BaseFrozenWithFrozenDefault):
|
||||
b: str = "2"
|
||||
|
||||
|
||||
@my_dataclass_frozen_default()
|
||||
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B2</error>(BaseNonFrozenImplicit):
|
||||
b: str = "2"
|
||||
@@ -0,0 +1,16 @@
|
||||
from decorator import my_dataclass, my_dataclass_frozen_default
|
||||
|
||||
|
||||
@my_dataclass(frozen=True)
|
||||
class BaseFrozenExplicit:
|
||||
a: int = 1
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class BaseNonFrozenImplicit:
|
||||
a: int = 1
|
||||
|
||||
|
||||
@my_dataclass_frozen_default()
|
||||
class BaseFrozenWithFrozenDefault:
|
||||
a: int = 1
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,26 @@
|
||||
from collections import OrderedDict
|
||||
from typing import ClassVar
|
||||
|
||||
from decorator import my_dataclass, field
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class A:
|
||||
a: list[int] = <error descr="Mutable default '[]' is not allowed. Use 'default_factory'">[]</error>
|
||||
b: list[int] = <error descr="Mutable default 'list()' is not allowed. Use 'default_factory'">list()</error>
|
||||
c: set[int] = <error descr="Mutable default '{1}' is not allowed. Use 'default_factory'">{1}</error>
|
||||
d: set[int] = <error descr="Mutable default 'set()' is not allowed. Use 'default_factory'">set()</error>
|
||||
e: tuple[int, ...] = ()
|
||||
f: tuple[int, ...] = tuple()
|
||||
g: ClassVar[list[int]] = []
|
||||
h: ClassVar = []
|
||||
i: dict[int, int] = <error descr="Mutable default '{1: 2}' is not allowed. Use 'default_factory'">{1: 2}</error>
|
||||
j: dict[int, int] = <error descr="Mutable default 'dict()' is not allowed. Use 'default_factory'">dict()</error>
|
||||
k = []
|
||||
l = list()
|
||||
m: dict[int, int] = <error descr="Mutable default 'OrderedDict()' is not allowed. Use 'default_factory'">OrderedDict()</error>
|
||||
n: frozenset[int] = frozenset()
|
||||
o: list = field(default_factory=list)
|
||||
a2: type[list[int]] = list
|
||||
b2: type[set[int]] = set
|
||||
c2: type[tuple[int, ...]] = tuple
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,69 @@
|
||||
from decorator import my_dataclass, my_dataclass_frozen_default
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class B1:
|
||||
x: int
|
||||
y: str
|
||||
z: float = 0.0
|
||||
|
||||
|
||||
B1.x = 5
|
||||
b1 = B1(1, "2")
|
||||
b1.x = 2
|
||||
b1.y = "3"
|
||||
b1.z = 1.0
|
||||
del b1.x
|
||||
del b1.y
|
||||
del b1.z
|
||||
|
||||
|
||||
@my_dataclass(frozen=False)
|
||||
class B2:
|
||||
x: int
|
||||
y: str
|
||||
z: float = 0.0
|
||||
|
||||
|
||||
B2.x = 5
|
||||
b2 = B2(1, "2")
|
||||
b2.x = 2
|
||||
b2.y = "3"
|
||||
b2.z = 1.0
|
||||
del b2.x
|
||||
del b2.y
|
||||
del b2.z
|
||||
|
||||
|
||||
@my_dataclass(frozen=True)
|
||||
class B3:
|
||||
x: int
|
||||
y: str
|
||||
z: float = 0.0
|
||||
|
||||
|
||||
B3.x = 5
|
||||
b3 = B3(1, "2")
|
||||
<error descr="'B3' object attribute 'x' is read-only">b3.x</error> = 2
|
||||
<error descr="'B3' object attribute 'y' is read-only">b3.y</error> = "3"
|
||||
<error descr="'B3' object attribute 'z' is read-only">b3.z</error> = 1.0
|
||||
del <error descr="'B3' object attribute 'x' is read-only">b3.x</error>
|
||||
del <error descr="'B3' object attribute 'y' is read-only">b3.y</error>
|
||||
del <error descr="'B3' object attribute 'z' is read-only">b3.z</error>
|
||||
|
||||
|
||||
@my_dataclass_frozen_default()
|
||||
class B4:
|
||||
x: int
|
||||
y: str
|
||||
z: float = 0.0
|
||||
|
||||
|
||||
B4.x = 5
|
||||
b4 = B4(1, "2")
|
||||
<error descr="'B4' object attribute 'x' is read-only">b4.x</error> = 2
|
||||
<error descr="'B4' object attribute 'y' is read-only">b4.y</error> = "3"
|
||||
<error descr="'B4' object attribute 'z' is read-only">b4.z</error> = 1.0
|
||||
del <error descr="'B4' object attribute 'x' is read-only">b4.x</error>
|
||||
del <error descr="'B4' object attribute 'y' is read-only">b4.y</error>
|
||||
del <error descr="'B4' object attribute 'z' is read-only">b4.z</error>
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,11 @@
|
||||
from bases import BaseFrozenWithFrozenDefault
|
||||
from decorator import my_dataclass
|
||||
|
||||
|
||||
@my_dataclass(frozen=True)
|
||||
class B1(BaseFrozenWithFrozenDefault):
|
||||
b: str = "1"
|
||||
|
||||
<error descr="'BaseFrozenWithFrozenDefault' object attribute 'a' is read-only">BaseFrozenWithFrozenDefault().a</error> = 2
|
||||
<error descr="'B1' object attribute 'a' is read-only">B1().a</error> = 2
|
||||
<error descr="'B1' object attribute 'b' is read-only">B1().b</error> = "2"
|
||||
@@ -0,0 +1,6 @@
|
||||
from decorator import my_dataclass, my_dataclass_frozen_default
|
||||
|
||||
|
||||
@my_dataclass_frozen_default()
|
||||
class BaseFrozenWithFrozenDefault:
|
||||
a: int = 1
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -0,0 +1,21 @@
|
||||
from decorator import my_dataclass_order_default, my_dataclass
|
||||
|
||||
|
||||
@my_dataclass(<error descr="'eq' must be true if 'order' is true">eq=False</error>, order=True)
|
||||
class A1:
|
||||
x: int
|
||||
|
||||
|
||||
@my_dataclass(eq=False)
|
||||
class A2:
|
||||
x: int
|
||||
|
||||
|
||||
@my_dataclass(eq=False, order=False)
|
||||
class A3:
|
||||
x: int
|
||||
|
||||
|
||||
@my_dataclass_order_default(<error descr="'eq' must be true if 'order' is true">eq=False</error>)
|
||||
class A4:
|
||||
x: int
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
@@ -30,3 +30,10 @@ class Derived3(Base3):
|
||||
|
||||
Derived3(<arg3>)
|
||||
Base3(<arg4>)
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Base4:
|
||||
a: int = field(kw_only=False)
|
||||
b: int
|
||||
|
||||
Base4(<arg5>)
|
||||
@@ -0,0 +1,24 @@
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def field(**kwargs):
|
||||
...
|
||||
|
||||
def not_field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class Dataclass:
|
||||
field1: int = field()
|
||||
field2: int = field(default=42)
|
||||
field3: int = not_field()
|
||||
field4: int = not_field(default=42)
|
||||
|
||||
|
||||
Dataclass(<arg1>)
|
||||
@@ -0,0 +1,21 @@
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def field_init_default_false(init: bool = False):
|
||||
...
|
||||
|
||||
def field(**kwargs):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(field_init_default_false, field))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@my_dataclass()
|
||||
class Dataclass:
|
||||
not_init_spec_default: int = field_init_default_false()
|
||||
not_init_spec_param: int = field(init=False)
|
||||
init_spec_param: int = field(init=True)
|
||||
init_inferred: int = field()
|
||||
|
||||
Dataclass(<arg1>)
|
||||
@@ -0,0 +1,76 @@
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def field_kw_only_default_false(kw_only: bool = False):
|
||||
...
|
||||
|
||||
def field_kw_only_default_true(kw_only: bool = True):
|
||||
...
|
||||
|
||||
def field_no_own_params():
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@dataclass_transform(kw_only_default=True, field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
|
||||
def my_dataclass_kw_only_default_true(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform(kw_only_default=False, field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
|
||||
def my_dataclass_kw_only_default_false(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
|
||||
@my_dataclass(kw_only=True)
|
||||
class DataclassKwOnlyTrue:
|
||||
not_kw_only_spec_default: int = field_kw_only_default_false()
|
||||
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
|
||||
kw_only_inferred: int = field_no_own_params()
|
||||
kw_only_spec_default: int = field_kw_only_default_true()
|
||||
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
|
||||
|
||||
|
||||
@my_dataclass_kw_only_default_true()
|
||||
class DataclassKwOnlyDefaultTrue:
|
||||
not_kw_only_spec_default: int = field_kw_only_default_false()
|
||||
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
|
||||
kw_only_inferred: int = field_no_own_params()
|
||||
kw_only_spec_default: int = field_kw_only_default_true()
|
||||
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
|
||||
|
||||
|
||||
@my_dataclass(kw_only=False)
|
||||
class DataclassKwOnlyFalse:
|
||||
not_kw_only_spec_default: int = field_kw_only_default_false()
|
||||
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
|
||||
not_kw_only_inferred: int = field_no_own_params()
|
||||
kw_only_spec_default: int = field_kw_only_default_true()
|
||||
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
|
||||
|
||||
|
||||
@my_dataclass_kw_only_default_false()
|
||||
class DataclassKwOnlyDefaultFalse:
|
||||
not_kw_only_spec_default: int = field_kw_only_default_false()
|
||||
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
|
||||
not_kw_only_inferred: int = field_no_own_params()
|
||||
kw_only_spec_default: int = field_kw_only_default_true()
|
||||
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class DataclassImplicitKwOnlyFalse:
|
||||
not_kw_only_spec_default: int = field_kw_only_default_false()
|
||||
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
|
||||
not_kw_only_inferred: int = field_no_own_params()
|
||||
kw_only_spec_default: int = field_kw_only_default_true()
|
||||
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
|
||||
|
||||
|
||||
DataclassKwOnlyTrue(<arg1>)
|
||||
DataclassKwOnlyDefaultTrue(<arg2>)
|
||||
DataclassKwOnlyFalse(<arg3>)
|
||||
DataclassKwOnlyDefaultFalse(<arg4>)
|
||||
DataclassImplicitKwOnlyFalse(<arg5>)
|
||||
@@ -0,0 +1,22 @@
|
||||
from typing import dataclass_transform, Callable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
|
||||
...
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class Super:
|
||||
super_attr: int
|
||||
|
||||
|
||||
@my_dataclass()
|
||||
class Sub(Super):
|
||||
sub_attr: int
|
||||
super_attr: str
|
||||
|
||||
|
||||
Sub(<arg1>)
|
||||
2
python/testData/stubs/DataclassFieldSpecifierStub.py
Normal file
2
python/testData/stubs/DataclassFieldSpecifierStub.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class Customer:
|
||||
attr = field(default_factory=list, init=False, kw_only=False, alias="alias")
|
||||
@@ -0,0 +1,13 @@
|
||||
import typing
|
||||
|
||||
@typing.dataclass_transform(kw_only_default=True, order_default=True)
|
||||
def create_model(
|
||||
*,
|
||||
frozen: bool = False,
|
||||
kw_only: bool = True,
|
||||
) -> Callable[[Type[_T]], Type[_T]]: ...
|
||||
|
||||
@create_model(frozen=True, kw_only=False)
|
||||
class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
@@ -0,0 +1,23 @@
|
||||
import typing
|
||||
|
||||
@typing.dataclass_transform(eq_default=True, order_default=True)
|
||||
class ModelBase:
|
||||
def __init_subclass__(
|
||||
cls,
|
||||
*,
|
||||
init: bool = True,
|
||||
frozen: bool = False,
|
||||
eq: bool = True,
|
||||
order: bool = True,
|
||||
):
|
||||
...
|
||||
|
||||
class CustomerModel(
|
||||
ModelBase,
|
||||
init=False,
|
||||
frozen=True,
|
||||
eq=False,
|
||||
order=False,
|
||||
):
|
||||
id: int
|
||||
name: str
|
||||
8
python/testData/stubs/DataclassTransformDecoratorStub.py
Normal file
8
python/testData/stubs/DataclassTransformDecoratorStub.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import dataclass_transform
|
||||
from mod1 import field1
|
||||
import mod2
|
||||
from mod3 import *
|
||||
|
||||
@dataclass_transform(eq_default=False, order_default=True, field_specifiers=(field1, mod2.field2, field3)
|
||||
def func()
|
||||
...
|
||||
@@ -1195,12 +1195,13 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
|
||||
// PY-49946
|
||||
public void testInitializingDataclassKwOnlyOnField() {
|
||||
final Map<String, PsiElement> marks = loadTest(4);
|
||||
final Map<String, PsiElement> marks = loadTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("b: int, *, a: int", new String[]{"b: int, "});
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a: int, *, b: int", new String[]{"a: int, "});
|
||||
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("*, a: int, b: int", new String[]{"*, a: int"});
|
||||
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("*, a: int", new String[]{"*, a: int"});
|
||||
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("a: int, *, b: int", new String[]{"a: int, "});
|
||||
}
|
||||
|
||||
// PY-53693
|
||||
@@ -1215,6 +1216,38 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg6>").getTextOffset()).check("a: str", new String[]{"a: str"});
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testInitializingDataclassTransformFieldSpecifierKwOnlyArgument() {
|
||||
final Map<String, PsiElement> marks = loadTest(5);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("not_kw_only_spec_default: int, not_kw_only_spec_arg: int, *, kw_only_inferred: int, kw_only_spec_default: int, kw_only_spec_arg: int", new String[]{"not_kw_only_spec_default: int, "});
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("not_kw_only_spec_default: int, not_kw_only_spec_arg: int, *, kw_only_inferred: int, kw_only_spec_default: int, kw_only_spec_arg: int", new String[]{"not_kw_only_spec_default: int, "});
|
||||
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("not_kw_only_spec_default: int, not_kw_only_spec_arg: int, not_kw_only_inferred: int, *, kw_only_spec_default: int, kw_only_spec_arg: int", new String[]{"not_kw_only_spec_default: int, "});
|
||||
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("not_kw_only_spec_default: int, not_kw_only_spec_arg: int, not_kw_only_inferred: int, *, kw_only_spec_default: int, kw_only_spec_arg: int", new String[]{"not_kw_only_spec_default: int, "});
|
||||
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("not_kw_only_spec_default: int, not_kw_only_spec_arg: int, not_kw_only_inferred: int, *, kw_only_spec_default: int, kw_only_spec_arg: int", new String[]{"not_kw_only_spec_default: int, "});
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testInitializingDataclassTransformFieldSpecifierInitArgument() {
|
||||
final Map<String, PsiElement> marks = loadTest(1);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("init_spec_param: int, init_inferred: int", new String[]{"init_spec_param: int, "});
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testInitializingDataclassTransformDistinguishingFieldSpecifierFromDefaults() {
|
||||
final Map<String, PsiElement> marks = loadTest(1);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("field1: int, field2: int = ..., field3: int = not_field(), field4: int = not_field(default=42)", new String[]{"field1: int, "});
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testInitializingDataclassTransformOverridingAncestorFieldType() {
|
||||
final Map<String, PsiElement> marks = loadTest(1);
|
||||
|
||||
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("super_attr: str, sub_attr: int", new String[]{"super_attr: str, "});
|
||||
}
|
||||
|
||||
// PY-49946
|
||||
public void testInitializingDataclassKwOnlyOnClassOverridingHierarchy() {
|
||||
final Map<String, PsiElement> marks = loadTest(3);
|
||||
|
||||
@@ -981,7 +981,7 @@ public class PyStubsTest extends PyTestCase {
|
||||
|
||||
assertTrue(file.findTopLevelClass("Foo1").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertFalse(file.findTopLevelClass("Foo2").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertFalse(file.findTopLevelClass("Foo3").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertNull(file.findTopLevelClass("Foo3").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
|
||||
assertNotParsed(file);
|
||||
}
|
||||
@@ -991,7 +991,7 @@ public class PyStubsTest extends PyTestCase {
|
||||
final PyFile file = getTestFile();
|
||||
final PyClass cls = file.findTopLevelClass("Foo");
|
||||
|
||||
assertFalse(cls.findClassAttribute("bar1", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertNull(cls.findClassAttribute("bar1", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertTrue(cls.findClassAttribute("bar2", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertFalse(cls.findClassAttribute("bar3", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
|
||||
@@ -1111,7 +1111,7 @@ public class PyStubsTest extends PyTestCase {
|
||||
|
||||
assertTrue(file.findTopLevelClass("Foo1").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertFalse(file.findTopLevelClass("Foo2").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertFalse(file.findTopLevelClass("Foo3").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
assertNull(file.findTopLevelClass("Foo3").getStub().getCustomStub(PyDataclassStub.class).kwOnly());
|
||||
|
||||
assertNotParsed(file);
|
||||
}
|
||||
@@ -1121,7 +1121,7 @@ public class PyStubsTest extends PyTestCase {
|
||||
final PyFile file = getTestFile();
|
||||
final PyClass cls = file.findTopLevelClass("Foo");
|
||||
|
||||
assertFalse(cls.findClassAttribute("bar1", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertNull(cls.findClassAttribute("bar1", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertTrue(cls.findClassAttribute("bar2", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
assertFalse(cls.findClassAttribute("bar3", false, null).getStub().getCustomStub(PyDataclassFieldStub.class).kwOnly());
|
||||
|
||||
@@ -1159,7 +1159,81 @@ public class PyStubsTest extends PyTestCase {
|
||||
assertNotNull(typeAliasStatement);
|
||||
doTestTypeParameterStub(typeAliasStatement, file);
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformDecoratorStub() {
|
||||
PyFile file = getTestFile();
|
||||
PyFunction decoratedFunction = file.findTopLevelFunction("func");
|
||||
assertNotNull(decoratedFunction);
|
||||
PyDecorator decorator = assertOneElement(decoratedFunction.getDecoratorList().getDecorators());
|
||||
PyDataclassTransformDecoratorStub customStub = decorator.getStub().getCustomStub(PyDataclassTransformDecoratorStub.class);
|
||||
assertNotNull(customStub);
|
||||
assertFalse(customStub.getEqDefault());
|
||||
assertTrue(customStub.getOrderDefault());
|
||||
assertFalse(customStub.getKwOnlyDefault());
|
||||
assertFalse(customStub.getFrozenDefault());
|
||||
assertEquals(
|
||||
List.of(
|
||||
QualifiedName.fromDottedString("field1"),
|
||||
QualifiedName.fromDottedString("mod2.field2"),
|
||||
QualifiedName.fromDottedString("field3")
|
||||
),
|
||||
customStub.getFieldSpecifiers());
|
||||
assertNotParsed(file);
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassFieldSpecifierStub() {
|
||||
PyFile file = getTestFile();
|
||||
@Nullable PyClass dataclass = file.findTopLevelClass("Customer");
|
||||
assertNotNull(dataclass);
|
||||
@Nullable PyTargetExpression attribute = dataclass.findClassAttribute("attr", false, TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile()));
|
||||
assertNotNull(attribute);
|
||||
PyDataclassFieldStub dataclassFieldStub = attribute.getStub().getCustomStub(PyDataclassFieldStub.class);
|
||||
assertNotNull(dataclassFieldStub);
|
||||
assertFalse(dataclassFieldStub.hasDefault());
|
||||
assertTrue(dataclassFieldStub.hasDefaultFactory());
|
||||
assertFalse(dataclassFieldStub.initValue());
|
||||
assertFalse(dataclassFieldStub.kwOnly());
|
||||
assertEquals(dataclassFieldStub.getAlias(), "alias");
|
||||
assertNotParsed(file);
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassStubForClassDecoratedWithDataclassTransformFactoryFunction() {
|
||||
PyFile file = getTestFile();
|
||||
PyClass modelClass = file.findTopLevelClass("CustomerModel");
|
||||
assertNotNull(modelClass);
|
||||
PyDataclassStub customStub = modelClass.getStub().getCustomStub(PyDataclassStub.class);
|
||||
assertNotNull(customStub);
|
||||
assertEquals("DATACLASS_TRANSFORM", customStub.getType());
|
||||
assertNull(customStub.initValue());
|
||||
assertNull(customStub.reprValue());
|
||||
assertNull(customStub.eqValue());
|
||||
assertNull(customStub.orderValue());
|
||||
assertNull(customStub.unsafeHashValue());
|
||||
assertTrue(customStub.frozenValue());
|
||||
assertFalse(customStub.kwOnly());
|
||||
assertNotParsed(file);
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassStubForClassExtendingDataclassTransformFactoryBase() {
|
||||
PyFile file = getTestFile();
|
||||
PyClass modelClass = file.findTopLevelClass("CustomerModel");
|
||||
assertNotNull(modelClass);
|
||||
PyDataclassStub customStub = modelClass.getStub().getCustomStub(PyDataclassStub.class);
|
||||
assertNotNull(customStub);
|
||||
assertEquals("DATACLASS_TRANSFORM", customStub.getType());
|
||||
assertFalse(customStub.initValue());
|
||||
assertNull(customStub.reprValue());
|
||||
assertFalse(customStub.eqValue());
|
||||
assertFalse(customStub.orderValue());
|
||||
assertNull(customStub.unsafeHashValue());
|
||||
assertTrue(customStub.frozenValue());
|
||||
assertNull(customStub.kwOnly());
|
||||
assertNotParsed(file);
|
||||
}
|
||||
|
||||
private void doTestTypingTypedDictArguments() {
|
||||
doTestTypedDict("name", Arrays.asList("x", "y"), Arrays.asList("str", "int"), QualifiedName.fromComponents("TypedDict"));
|
||||
|
||||
@@ -5518,6 +5518,192 @@ public class PyTypingTest extends PyTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformConstructorSignature() {
|
||||
doTestExpressionUnderCaret("(id: int, name: str) -> MyClass", """
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def deco(cls):
|
||||
...
|
||||
|
||||
@deco
|
||||
class MyClass:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
MyCl<caret>ass()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformConstructorSignatureDecoratedBaseClassAttributeExcluded() {
|
||||
doTestExpressionUnderCaret("(id: int, name: str) -> SubSub", """
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class Base:
|
||||
excluded: int
|
||||
|
||||
class Sub(Base):
|
||||
id: int
|
||||
|
||||
class SubSub(Sub):
|
||||
name: str
|
||||
|
||||
Sub<caret>Sub()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformConstructorSignatureMetaClassBaseClassAttributeNotExcluded() {
|
||||
doTestExpressionUnderCaret("(included: int, id: int, name: str) -> SubSub", """
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class Meta(type):
|
||||
pass
|
||||
|
||||
class Base(metaclass=Meta):
|
||||
included: int
|
||||
|
||||
class Sub(Base):
|
||||
id: int
|
||||
|
||||
class SubSub(Sub):
|
||||
name: str
|
||||
|
||||
Sub<caret>Sub()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformOverloads() {
|
||||
doTestExpressionUnderCaret("(id: int, name: str) -> MyClass", """
|
||||
from typing import dataclass_transform, overload
|
||||
|
||||
@overload
|
||||
def deco(name: str):
|
||||
...
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
@overload
|
||||
def deco(cls: type):
|
||||
...
|
||||
|
||||
@overload
|
||||
def deco():
|
||||
...
|
||||
|
||||
def deco(*args, **kwargs):
|
||||
...
|
||||
|
||||
@deco
|
||||
class MyClass:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
MyCl<caret>ass()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformOwnKwOnlyOmittedAndTakenFromKwOnlyDefault() {
|
||||
doTestExpressionUnderCaret("(Any, id: int, name: str) -> MyClass", """
|
||||
from typing import dataclass_transform, Callable
|
||||
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def deco(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
|
||||
@deco(frozen=True)
|
||||
class MyClass:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
My<caret>Class()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformFieldSpecifierKwOnlyDefaultOverridesDecoratorsKwOnly() {
|
||||
doTestExpressionUnderCaret("(id: str, Any, addr: list[str]) -> Order", """
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def my_field(kw_only=False):
|
||||
...
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@my_dataclass(kw_only=True)
|
||||
class Order:
|
||||
id: str = my_field()
|
||||
addr: list[str]
|
||||
|
||||
Ord<caret>er()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformFieldSpecifierKwOnlyDefaultOverridesDecoratorsKwOnlyDefault() {
|
||||
doTestExpressionUnderCaret("(id: str, Any, addr: list[str]) -> Order", """
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def my_field(kw_only=False):
|
||||
...
|
||||
|
||||
@dataclass_transform(kw_only_default=True, field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@my_dataclass()
|
||||
class Order:
|
||||
id: str = my_field()
|
||||
addr: list[str]
|
||||
|
||||
Ord<caret>er()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformFieldSpecifierKwOnlyOverridesDecoratorsKwOnly() {
|
||||
doTestExpressionUnderCaret("(id: str, Any, addr: list[str]) -> Order", """
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def my_field(kw_only=False):
|
||||
...
|
||||
|
||||
@dataclass_transform(field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@my_dataclass(kw_only=True)
|
||||
class Order:
|
||||
id: str = my_field(kw_only=False)
|
||||
addr: list[str]
|
||||
|
||||
Ord<caret>er()
|
||||
""");
|
||||
}
|
||||
|
||||
public void testDataclassTransformFieldSpecifierKwOnlyOverridesDecoratorsKwOnlyDefault() {
|
||||
doTestExpressionUnderCaret("(id: str, Any, addr: list[str]) -> Order", """
|
||||
from typing import Callable, dataclass_transform
|
||||
|
||||
def my_field(kw_only=False):
|
||||
...
|
||||
|
||||
@dataclass_transform(kw_only_default=True, field_specifiers=(my_field,))
|
||||
def my_dataclass(**kwargs) -> Callable[[type], type]:
|
||||
...
|
||||
|
||||
@my_dataclass()
|
||||
class Order:
|
||||
id: str = my_field(kw_only=False)
|
||||
addr: list[str]
|
||||
|
||||
Ord<caret>er()
|
||||
""");
|
||||
}
|
||||
|
||||
private void doTestNoInjectedText(@NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
|
||||
@@ -5547,6 +5733,15 @@ public class PyTypingTest extends PyTestCase {
|
||||
assertType("Failed in user initiated context", expectedType, expr, userInitiated);
|
||||
}
|
||||
|
||||
private void doTestExpressionUnderCaret(@NotNull String expectedType, @NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
PyExpression expr = PsiTreeUtil.getParentOfType(myFixture.getFile().findElementAt(myFixture.getCaretOffset()), PyExpression.class);
|
||||
TypeEvalContext codeAnalysis = TypeEvalContext.codeAnalysis(expr.getProject(), expr.getContainingFile());
|
||||
TypeEvalContext userInitiated = TypeEvalContext.userInitiated(expr.getProject(), expr.getContainingFile()).withTracing();
|
||||
assertType("Failed in code analysis context", expectedType, expr, codeAnalysis);
|
||||
assertType("Failed in user initiated context", expectedType, expr, userInitiated);
|
||||
}
|
||||
|
||||
private void doMultiFileStubAwareTest(@NotNull final String expectedType, @NotNull final String text) {
|
||||
myFixture.copyDirectoryToProject("types/" + getTestName(false), "");
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
|
||||
@@ -342,4 +342,16 @@ public class Py3ArgumentListInspectionTest extends PyInspectionTestCase {
|
||||
public void testFunctoolsWrapsMultiFile() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testInitByDataclassTransformOnDecorator() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testInitByDataclassTransformOnBaseClass() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testInitByDataclassTransformOnMetaClass() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,16 +18,31 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testMutatingFrozenDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28506
|
||||
public void testFrozenInheritance() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testFrozenInheritanceDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28506, PY-31762
|
||||
public void testMutatingFrozenInInheritance() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testMutatingFrozenInInheritanceDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28506, PY-31762
|
||||
public void testMutatingFrozenInMixedInheritance() {
|
||||
doTest();
|
||||
@@ -38,26 +53,46 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testOrderAndNotEqDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-27398
|
||||
public void testDefaultFieldValue() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testMutableDefaultFieldValueDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-27398
|
||||
public void testFieldsOrder() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
|
||||
// PY-26354
|
||||
public void testAttrsFieldsOrder() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformFieldsOrder() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28506, PY-31762
|
||||
public void testFieldsOrderInInheritance() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformFieldOrderInInheritance() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28506, PY-31762
|
||||
public void testFieldsOrderInMixedInheritance() {
|
||||
doTest();
|
||||
@@ -118,6 +153,26 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testComparisonForOrderedDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testComparisonForUnorderedDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testComparisonForOrderedAndUnorderedDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testComparisonForManuallyOrderedDataclassTransform() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-32078
|
||||
public void testComparisonForManuallyOrderedAttrs() {
|
||||
doTestByText("""
|
||||
@@ -250,11 +305,21 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformDefaultAndDefaultFactory() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-27398
|
||||
public void testUselessInitReprEq() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformUselessInitEqUnsafeHashOrderFrozen() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-27398
|
||||
public void testUselessOrder() {
|
||||
doTest();
|
||||
@@ -305,6 +370,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformFieldLackingTypeAnnotation() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-26354
|
||||
public void testAttrsLackingTypeAnnotation() {
|
||||
doTest();
|
||||
@@ -325,6 +395,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-54560
|
||||
public void testDataclassTransformKwOnlyFieldOrderInInheritance() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-49946
|
||||
public void testFieldsOrderOverridden() {
|
||||
doTest();
|
||||
@@ -338,6 +413,14 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
|
||||
assertProjectFilesNotParsed(myFixture.getFile());
|
||||
}
|
||||
|
||||
public void testFieldOrderInheritanceMultifile() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testDataclassMissingHandlingMultifile() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user