Skip to content

Commit 892b8d8

Browse files
authored
Fix dataclass plugin to work with new semantic analyzer (#6515)
Fixes #6324 This enables two test files that previously had few crashes. Changes in this PR: * Add `defer()` to public plugin API, otherwise dataclasses will not works with forward references. * Update one plugin test to use the new analyzer because the output is a bit more verbose, and probably not worth the hassle of making it identical to old analyzer. * One test is skipped because of existing issue #6454 * Add the generated type variable used for self types to the class body (this is identical to named tuples and `attrs` plugin).
1 parent b4a9399 commit 892b8d8

File tree

8 files changed

+48
-15
lines changed

8 files changed

+48
-15
lines changed

Diff for: mypy/plugin.py

+9
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,15 @@ def qualified_name(self, n: str) -> str:
253253
"""Make qualified name using current module and enclosing class (if any)."""
254254
raise NotImplementedError
255255

256+
@abstractmethod
257+
def defer(self) -> None:
258+
"""Call this to defer the processing of the current node.
259+
260+
This will request an additional iteration of semantic analysis.
261+
Only available with new semantic analyzer.
262+
"""
263+
raise NotImplementedError
264+
256265

257266
# A context for a function hook that infers the return type of a function with
258267
# a special signature.

Diff for: mypy/plugins/dataclasses.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from mypy.nodes import (
55
ARG_OPT, ARG_POS, MDEF, Argument, AssignmentStmt, CallExpr,
66
Context, Expression, FuncDef, JsonDict, NameExpr,
7-
SymbolTableNode, TempNode, TypeInfo, Var,
7+
SymbolTableNode, TempNode, TypeInfo, Var, TypeVarExpr
88
)
99
from mypy.plugin import ClassDefContext
1010
from mypy.plugins.common import add_method, _get_decorator_bool_argument
@@ -21,6 +21,8 @@
2121
'dataclasses.dataclass',
2222
} # type: Final
2323

24+
SELF_TVAR_NAME = '_DT' # type: Final
25+
2426

2527
class DataclassAttribute:
2628
def __init__(
@@ -77,6 +79,12 @@ def transform(self) -> None:
7779
ctx = self._ctx
7880
info = self._ctx.cls.info
7981
attributes = self.collect_attributes()
82+
if ctx.api.options.new_semantic_analyzer:
83+
# Check if attribute types are ready.
84+
for attr in attributes:
85+
if info[attr.name].type is None:
86+
ctx.api.defer()
87+
return
8088
decorator_arguments = {
8189
'init': _get_decorator_bool_argument(self._ctx, 'init', True),
8290
'eq': _get_decorator_bool_argument(self._ctx, 'eq', True),
@@ -92,14 +100,23 @@ def transform(self) -> None:
92100
return_type=NoneTyp(),
93101
)
94102

103+
if (decorator_arguments['eq'] and info.get('__eq__') is None or
104+
decorator_arguments['order']):
105+
# Type variable for self types in generated methods.
106+
obj_type = ctx.api.named_type('__builtins__.object')
107+
self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME,
108+
[], obj_type)
109+
info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr)
110+
95111
# Add an eq method, but only if the class doesn't already have one.
96112
if decorator_arguments['eq'] and info.get('__eq__') is None:
97113
for method_name in ['__eq__', '__ne__']:
98114
# The TVar is used to enforce that "other" must have
99115
# the same type as self (covariant). Note the
100116
# "self_type" parameter to add_method.
101117
obj_type = ctx.api.named_type('__builtins__.object')
102-
cmp_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type)
118+
cmp_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME,
119+
-1, [], obj_type)
103120
cmp_other_type = TypeVarType(cmp_tvar_def)
104121
cmp_return_type = ctx.api.named_type('__builtins__.bool')
105122

@@ -121,7 +138,8 @@ def transform(self) -> None:
121138
# Like for __eq__ and __ne__, we want "other" to match
122139
# the self type.
123140
obj_type = ctx.api.named_type('__builtins__.object')
124-
order_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type)
141+
order_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME,
142+
-1, [], obj_type)
125143
order_other_type = TypeVarType(order_tvar_def)
126144
order_return_type = ctx.api.named_type('__builtins__.bool')
127145
order_args = [

Diff for: mypy/semanal.py

+4
Original file line numberDiff line numberDiff line change
@@ -3818,6 +3818,10 @@ def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> None:
38183818
else:
38193819
self.globals[name] = stnode
38203820

3821+
def defer(self) -> None:
3822+
assert not self.options.new_semantic_analyzer
3823+
raise NotImplementedError('This is only available with --new-semantic-analyzer')
3824+
38213825

38223826
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:
38233827
if isinstance(sig, CallableType):

Diff for: mypy/test/hacks.py

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
new_semanal_blacklist = [
1010
'check-async-await.test',
1111
'check-classes.test',
12-
'check-custom-plugin.test',
13-
'check-dataclasses.test',
1412
'check-expressions.test',
1513
'check-flags.test',
1614
'check-functions.test',

Diff for: test-data/unit/check-custom-plugin.test

+7-5
Original file line numberDiff line numberDiff line change
@@ -491,16 +491,18 @@ class Instr(Generic[T]): ...
491491
plugins=<ROOT>/test-data/unit/plugins/dyn_class.py
492492

493493
[case testDynamicClassPluginNegatives]
494-
# flags: --config-file tmp/mypy.ini
494+
# flags: --new-semantic-analyzer --config-file tmp/mypy.ini
495495
from mod import declarative_base, Column, Instr, non_declarative_base
496496

497497
Bad1 = non_declarative_base()
498498
Bad2 = Bad3 = declarative_base()
499499

500-
class C1(Bad1): ... # E: Invalid base class
501-
class C2(Bad2): ... # E: Invalid base class
502-
class C3(Bad3): ... # E: Invalid base class
503-
500+
class C1(Bad1): ... # E: Invalid base class \
501+
# E: Invalid type "__main__.Bad1"
502+
class C2(Bad2): ... # E: Invalid base class \
503+
# E: Invalid type "__main__.Bad2"
504+
class C3(Bad3): ... # E: Invalid base class \
505+
# E: Invalid type "__main__.Bad3"
504506
[file mod.py]
505507
from typing import Generic, TypeVar
506508
def declarative_base(): ...

Diff for: test-data/unit/check-dataclasses.test

+2-1
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,9 @@ class Application:
393393

394394
[builtins fixtures/list.pyi]
395395

396+
-- Blocked by #6454
396397
[case testDataclassOrderingWithCustomMethods]
397-
# flags: --python-version 3.6
398+
# flags: --python-version 3.6 --no-new-semantic-analyzer
398399
from dataclasses import dataclass
399400

400401
@dataclass(order=True)

Diff for: test-data/unit/check-incremental.test

+4-4
Original file line numberDiff line numberDiff line change
@@ -3864,10 +3864,10 @@ class A:
38643864
tmp/b.py:3: error: Revealed type is 'def (a: builtins.int) -> a.A'
38653865
tmp/b.py:4: error: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool'
38663866
tmp/b.py:5: error: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool'
3867-
tmp/b.py:6: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool'
3868-
tmp/b.py:7: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool'
3869-
tmp/b.py:8: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool'
3870-
tmp/b.py:9: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool'
3867+
tmp/b.py:6: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool'
3868+
tmp/b.py:7: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool'
3869+
tmp/b.py:8: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool'
3870+
tmp/b.py:9: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool'
38713871
tmp/b.py:18: error: Unsupported operand types for < ("A" and "int")
38723872
tmp/b.py:19: error: Unsupported operand types for <= ("A" and "int")
38733873
tmp/b.py:20: error: Unsupported operand types for > ("A" and "int")

Diff for: test-data/unit/deps.test

+1
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ class B(A):
14001400

14011401
[out]
14021402
<m.A.(abstract)> -> <m.B.__init__>, m
1403+
<m.A._DT> -> <m.B._DT>
14031404
<m.A.__eq__> -> <m.B.__eq__>
14041405
<m.A.__init__> -> <m.B.__init__>, m.B.__init__
14051406
<m.A.__ne__> -> <m.B.__ne__>

0 commit comments

Comments
 (0)