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.

GitOrigin-RevId: de9afeb0831a52f058453fe678de229d41c26a4d
This commit is contained in:
Mikhail Golubev
2024-12-11 16:48:52 +00:00
committed by intellij-monorepo-bot
parent 5af3db527d
commit 91986e49c5
10 changed files with 51 additions and 7 deletions
@@ -566,7 +566,7 @@ private fun resolveDataclassParameters(
if (dataclassTransformStub != null) {
val resolvedFieldSpecifiers = dataclassTransformStub.fieldSpecifiers
.flatMap { PyResolveUtil.resolveQualifiedNameInScope(it, ScopeUtil.getScopeOwner(pyClass)!!, context) }
.flatMap { PyResolveUtil.resolveQualifiedNameInScope(it, dataclassTransformDecorator.containingFile as ScopeOwner, context) }
.filterIsInstance<PyQualifiedNameOwner>()
.mapNotNull { it.qualifiedName }
.map { QualifiedName.fromDottedString(it) }
@@ -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
@@ -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)
@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...
@@ -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)
@@ -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
@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...
@@ -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)
@@ -0,0 +1,10 @@
import dataclasses
class DataclassField:
def __init__(self,
*,
kw_only=dataclasses.MISSING,
default=dataclasses.MISSING,
default_factory=dataclasses.MISSING):
...
@@ -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,