PY-77433 Fix resolving qualified names in field_specifiers argument of @dataclass_transform

Previously, we mistakenly tried to resolve qualified names listed in the
`field_specifiers` argument of @dataclass_transform in the same scope
where the dataclass itself is defined, not where the actual decorator
application is located.

Thus, if in the file where the dataclass is defined, a field specifier
was imported differently than where @dataclass_transform was applied, we
couldn't recognize a field specifier call in the RHS of an assignment as such
and took it for an ordinary field default value.

In particular, this is what happened with pydantic dataclasses.
`pydantic.fields.Field` is usually imported as just `pydantic.Field` where
user dataclasses are defined, but imported with an alias and set in the
`field_specifiers` argument as `PydanticModelField` in
`pydantic._internal._model_construction` where `ModelMetaclass` is defined.

It was accidentally broken in f15a07836e7aeac7c46b489b4742e8248a0e6ef4 to
support decorating class methods with dataclass_transform
(see testData/inspections/PyDataclassInspection/DataclassTransformFieldsOrder/decorator.py).
Until PyResolveUtil.resolveQualifiedNameInScope automatically traverses through
containing scopes looking for a name, the file containing decorator application
seems like a safe trade-off for the scope, because field specifiers are normally
defined or imported somewhere at the top level.


(cherry picked from commit de9afeb0831a52f058453fe678de229d41c26a4d)

IJ-CR-151380

GitOrigin-RevId: b6576ec7b72ea1e19e93b6190372a5168003c396
This commit is contained in:
Mikhail Golubev
2024-12-10 11:43:26 +02:00
committed by intellij-monorepo-bot
parent a45d559f88
commit ab6adac4d4
10 changed files with 51 additions and 7 deletions

View File

@@ -1,7 +1,9 @@
from typing import dataclass_transform
import dt_field
@dataclass_transform()
@dataclass_transform(field_specifiers=(dt_field.DataclassField,))
class DataclassBase:
def __init_subclass__(cls, **kwargs):
import dataclasses

View File

@@ -1,6 +1,10 @@
from dt_base import DataclassBase
from dt_field import DataclassField
class RecordViaBaseClass(DataclassBase, kw_only=True):
id: int
name: str
name: str = DataclassField()
address: str | None = DataclassField(default=None)

View File

@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...

View File

@@ -1,7 +1,9 @@
from dt_decorator import my_dt_decorator
from dt_field import DataclassField
@my_dt_decorator(kw_only=True)
class RecordViaDecorator:
id: int
name: str
name: str = DataclassField()
address: str | None = DataclassField(default=None)

View File

@@ -1,7 +1,9 @@
from typing import dataclass_transform, Callable
import dt_field
@dataclass_transform()
@dataclass_transform(field_specifiers=(dt_field.DataclassField,))
def my_dt_decorator(**kwargs) -> Callable[[type], type]:
import dataclasses
return dataclasses.dataclass(**kwargs) # type: ignore

View File

@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...

View File

@@ -1,6 +1,8 @@
from dt_base import DataclassBase
from dt_field import DataclassField
class RecordViaMetaClass(DataclassBase, kw_only=True):
id: int
name: str
name: str = DataclassField()
address: str | None = DataclassField(default=None)

View File

@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...

View File

@@ -1,7 +1,9 @@
from typing import dataclass_transform
import dt_field
@dataclass_transform()
@dataclass_transform(field_specifiers=(dt_field.DataclassField,))
class DataclassMeta(type):
def __new__(
cls,