PY-76149 Support descriptor types as annotations for dataclass fields

(cherry picked from commit 78a127e1a0083ece810ad996124ad6ea65887da2)

GitOrigin-RevId: 0c33fe51f5f13116f773577056317c537cbc83ef
This commit is contained in:
Daniil Kalinin
2024-10-02 18:52:05 +03:00
committed by intellij-monorepo-bot
parent 4ad6f08f45
commit c653a43cab
4 changed files with 112 additions and 0 deletions

View File

@@ -254,6 +254,12 @@ class PyDataclassTypeProvider : PyTypeProviderBase() {
if (type is PyCollectionType && type.classQName == Dataclasses.DATACLASSES_INITVAR) {
return type.elementTypes.firstOrNull()
}
if (type is PyClassLikeType) {
val expectedConstructorArgumentTypeRef = PyDescriptorTypeUtil.getExpectedValueTypeForDunderSet(field, type, context)
if (expectedConstructorArgumentTypeRef != null) {
return Ref.deref(expectedConstructorArgumentTypeRef)
}
}
if (type == null && dataclassType.asPredefinedType == PyDataclassParameters.PredefinedType.ATTRS) {
methodDecoratedAsAttributeDefault(cls, field.name)

View File

@@ -0,0 +1,20 @@
from typing import dataclass_transform, TypeVar, Generic
T = TypeVar("T")
@dataclass_transform()
def deco(cls):
...
class MyDescriptor(Generic[T]):
def __set__(self, obj: object, value: T) -> None:
...
@deco
class MyClass:
id: MyDescriptor[int]
name: MyDescriptor[str]
year: MyDescriptor[int]
new: MyDescriptor[bool]
MyClass(<arg1>)

View File

@@ -1345,6 +1345,12 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
feignCtrlPWithHintsForHighlightedOnly(marks.get("<arg1>").getTextOffset()).check("a, b, c: str = \"default\"", new String[]{"c: str = \"default\""});
}
// PY-76149
public void testDataclassTransformConstructorSignatureWithFieldsAnnotatedWithGenericDescriptor() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("id: int, name: str, year: int, new: bool", new String[]{"id: int, "});
}
@NotNull
private Collector feignCtrlP(int offset) {
return feignCtrlP(offset, myFixture.getFile(), true, myFixture.getEditor());

View File

@@ -5970,6 +5970,86 @@ public class PyTypingTest extends PyTestCase {
});
}
// PY-76149
public void testDataclassTransformConstructorSignatureWithFieldsAnnotatedWithDescriptor() {
doTestExpressionUnderCaret("(id: int, name: str) -> MyClass", """
from typing import dataclass_transform
@dataclass_transform()
def deco(cls):
...
class MyIdDescriptor:
def __set__(self, obj: object, value: int) -> None:
...
class MyNameDescriptor:
def __set__(self, obj: object, value: str) -> None:
...
@deco
class MyClass:
id: MyIdDescriptor
name: MyNameDescriptor
MyCl<caret>ass()
""");
}
// PY-76149
public void testDataclassTransformConstructorSignatureWithFieldsAnnotatedWithGenericDescriptor() {
doTestExpressionUnderCaret("(id: int, name: str) -> MyClass", """
from typing import dataclass_transform, TypeVar, Generic
T = TypeVar("T")
@dataclass_transform()
def deco(cls):
...
class MyDescriptor(Generic[T]):
def __set__(self, obj: object, value: T) -> None:
...
@deco
class MyClass:
id: MyDescriptor[int]
name: MyDescriptor[str]
MyCl<caret>ass()
""");
}
// PY-76149
public void testDataclassTransformConstructorSignatureWithFieldsAnnotatedWitExplicitAny() {
doTestExpressionUnderCaret("(id: int, name: str, payload: Any, payload_length: int) -> MyClass", """
from typing import dataclass_transform, TypeVar, Generic, Any
T = TypeVar("T")
@dataclass_transform()
def deco(cls):
...
class MyDescriptor(Generic[T]):
def __set__(self, obj: object, value: T) -> None:
...
class Anything:
def __set__(self, obj: object, value: Any) -> None:
...
@deco
class MyClass:
id: MyDescriptor[int]
name: MyDescriptor[str]
payload: Anything
payload_length: MyDescriptor[int]
My<caret>Class()
""");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());