Provide callable type for dataclass initialization (PY-27398)

Add `dataclasses` to stdlib_packages.txt
This commit is contained in:
Semyon Proshev
2018-01-09 14:39:22 +03:00
parent d47336127e
commit 3316258201
13 changed files with 117 additions and 1 deletions
+1
View File
@@ -55,6 +55,7 @@ csv
ctypes
curses
datetime
dataclasses
dbhash
dbm
decimal
@@ -703,6 +703,7 @@
<pyClassMembersProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibClassMembersProvider"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibTypeProvider"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibOverridingTypeProvider"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyDataclassesTypeProvider"/>
<pyModuleMembersProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibModuleMembersProvider"/>
<documentationLinkProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibDocumentationLinkProvider"/>
<canonicalPathProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibCanonicalPathProvider"/>
@@ -0,0 +1,46 @@
/*
* Copyright 2000-2017 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.codeInsight.stdlib
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyCallExpressionNavigator
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.types.*
class PyDataclassesTypeProvider : PyTypeProviderBase() {
override fun getReferenceExpressionType(referenceExpression: PyReferenceExpression, context: TypeEvalContext): PyType? {
return getDataclassTypeForCallee(referenceExpression, context)
}
private fun getDataclassTypeForCallee(referenceExpression: PyReferenceExpression, context: TypeEvalContext): PyCallableType? {
if (PyCallExpressionNavigator.getPyCallExpressionByCallee(referenceExpression) == null) return null
val resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context)
val resolveResults = referenceExpression.getReference(resolveContext).multiResolve(false)
return PyUtil.filterTopPriorityResults(resolveResults)
.asSequence()
.filterIsInstance<PyClass>()
.map { getDataclassTypeForClass(it, context) }
.firstOrNull { it != null }
}
private fun getDataclassTypeForClass(cls: PyClass, context: TypeEvalContext): PyCallableType? {
if (!PyKnownDecoratorUtil.getKnownDecorators(cls, context).contains(PyKnownDecoratorUtil.KnownDecorator.DATACLASSES_DATACLASS)) {
return null
}
val parameters = ArrayList<PyCallableParameter>()
cls.processClassLevelDeclarations { element, _ ->
if (element is PyTargetExpression && element.annotationValue != null) {
parameters.add(PyCallableParameterImpl.nonPsi(element.name, context.getType(element), element.findAssignedValue()))
}
true
}
return PyCallableTypeImpl(parameters, context.getType(cls))
}
}
@@ -67,7 +67,9 @@ public class PyKnownDecoratorUtil {
PYRAMID_DECORATOR_REIFY("pyramid.decorator.reify"),
DJANGO_UTILS_FUNCTIONAL_CACHED_PROPERTY("django.utils.functional.cached_property"),
KOMBU_UTILS_CACHED_PROPERTY("kombu.utils.cached_property");
KOMBU_UTILS_CACHED_PROPERTY("kombu.utils.cached_property"),
DATACLASSES_DATACLASS("dataclasses.dataclass");
private final QualifiedName myQualifiedName;
@@ -0,0 +1,13 @@
import dataclasses
@dataclasses.dataclass
class A:
x: int
y: str
z: float = 0.0
A(<warning descr="Parameter 'x' unfilled"><warning descr="Parameter 'y' unfilled">)</warning></warning>
A(1<warning descr="Parameter 'y' unfilled">)</warning>
A(1, "a")
A(1, "a", 1.0)
A(1, "a", 1.0, <warning descr="Unexpected argument">"b"</warning>)
@@ -0,0 +1,3 @@
def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,
hash=None, frozen=False):
pass
@@ -0,0 +1,13 @@
import dataclasses
@dataclasses.dataclass
class A:
x: int
y: str
z: float = 0.0
A(1, "a")
A(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>, <warning descr="Expected type 'str', got 'int' instead">1</warning>)
A(1, "a", 1.0)
A(<warning descr="Expected type 'int', got 'str' instead">"a"</warning>, <warning descr="Expected type 'str', got 'int' instead">1</warning>, <warning descr="Expected type 'float', got 'str' instead">"b"</warning>)
@@ -0,0 +1,3 @@
def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,
hash=None, frozen=False):
pass
@@ -0,0 +1,9 @@
import dataclasses
@dataclasses.dataclass
class A:
x: int
y: str
z: float = 0.0
A(<arg1>)
@@ -0,0 +1,3 @@
def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,
hash=None, frozen=False):
pass
@@ -707,6 +707,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
);
}
// PY-27398
public void testInitializingDataclass() {
runWithLanguageLevel(
LanguageLevel.PYTHON37,
() -> {
final Map<String, PsiElement> marks = loadMultiFileTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("x: int, y: str, z: float=0.0", new String[]{"x: int, "});
}
);
}
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where Ctrl+P is pressed.
@@ -276,4 +276,9 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
public void testChainedComparisonsGenericMatching() {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}
// PY-27398
public void testInitializingDataclass() {
runWithLanguageLevel(LanguageLevel.PYTHON37, () -> super.doMultiFileTest());
}
}
@@ -318,4 +318,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
public void testAbstractMethod() {
runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest);
}
// PY-27398
public void testInitializingDataclass() {
runWithLanguageLevel(LanguageLevel.PYTHON37, this::doMultiFileTest);
}
}