From 8516a8ded6dd26809229c00b02f7a251dfa8a202 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Mar 2019 15:30:39 +0000 Subject: [PATCH 1/2] Fix dataclass plugin to work with new semantic analyzer --- mypy/plugin.py | 9 +++++++++ mypy/plugins/dataclasses.py | 24 +++++++++++++++++++++--- mypy/semanal.py | 4 ++++ mypy/test/hacks.py | 2 -- test-data/unit/check-custom-plugin.test | 12 +++++++----- test-data/unit/check-dataclasses.test | 3 ++- test-data/unit/deps.test | 1 + 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/mypy/plugin.py b/mypy/plugin.py index 1d51fe418c848..7f08b4e2329c6 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -253,6 +253,15 @@ def qualified_name(self, n: str) -> str: """Make qualified name using current module and enclosing class (if any).""" raise NotImplementedError + @abstractmethod + def defer(self) -> None: + """Call this to defer the processing of the current node. + + This will request an additional iteration of semantic analysis. + Only available with new semantic analyzer. + """ + raise NotImplementedError + # A context for a function hook that infers the return type of a function with # a special signature. diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 142a28bc0c72b..dc23b3f42454a 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -4,7 +4,7 @@ from mypy.nodes import ( ARG_OPT, ARG_POS, MDEF, Argument, AssignmentStmt, CallExpr, Context, Expression, FuncDef, JsonDict, NameExpr, - SymbolTableNode, TempNode, TypeInfo, Var, + SymbolTableNode, TempNode, TypeInfo, Var, TypeVarExpr ) from mypy.plugin import ClassDefContext from mypy.plugins.common import add_method, _get_decorator_bool_argument @@ -21,6 +21,8 @@ 'dataclasses.dataclass', } # type: Final +SELF_TVAR_NAME = '_DT' # type: Final + class DataclassAttribute: def __init__( @@ -77,6 +79,12 @@ def transform(self) -> None: ctx = self._ctx info = self._ctx.cls.info attributes = self.collect_attributes() + if ctx.api.options.new_semantic_analyzer: + # Check if attribute types are ready. + for attr in attributes: + if info[attr.name].type is None: + ctx.api.defer() + return decorator_arguments = { 'init': _get_decorator_bool_argument(self._ctx, 'init', True), 'eq': _get_decorator_bool_argument(self._ctx, 'eq', True), @@ -92,6 +100,14 @@ def transform(self) -> None: return_type=NoneTyp(), ) + if (decorator_arguments['eq'] and info.get('__eq__') is None or + decorator_arguments['order']): + # Type variable for self types in generated methods. + obj_type = ctx.api.named_type('__builtins__.object') + self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, + [], obj_type) + info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) + # Add an eq method, but only if the class doesn't already have one. if decorator_arguments['eq'] and info.get('__eq__') is None: for method_name in ['__eq__', '__ne__']: @@ -99,7 +115,8 @@ def transform(self) -> None: # the same type as self (covariant). Note the # "self_type" parameter to add_method. obj_type = ctx.api.named_type('__builtins__.object') - cmp_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type) + cmp_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, + -1, [], obj_type) cmp_other_type = TypeVarType(cmp_tvar_def) cmp_return_type = ctx.api.named_type('__builtins__.bool') @@ -121,7 +138,8 @@ def transform(self) -> None: # Like for __eq__ and __ne__, we want "other" to match # the self type. obj_type = ctx.api.named_type('__builtins__.object') - order_tvar_def = TypeVarDef('T', 'T', -1, [], obj_type) + order_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, + -1, [], obj_type) order_other_type = TypeVarType(order_tvar_def) order_return_type = ctx.api.named_type('__builtins__.bool') order_args = [ diff --git a/mypy/semanal.py b/mypy/semanal.py index 20ee6866a0c44..5e503b3e5b3fc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3818,6 +3818,10 @@ def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> None: else: self.globals[name] = stnode + def defer(self) -> None: + assert not self.options.new_semantic_analyzer + raise NotImplementedError('This is only available with --new-semantic-analyzer') + def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: if isinstance(sig, CallableType): diff --git a/mypy/test/hacks.py b/mypy/test/hacks.py index dc1ccd3bb1b31..6d02637866316 100644 --- a/mypy/test/hacks.py +++ b/mypy/test/hacks.py @@ -9,8 +9,6 @@ new_semanal_blacklist = [ 'check-async-await.test', 'check-classes.test', - 'check-custom-plugin.test', - 'check-dataclasses.test', 'check-enum.test', 'check-expressions.test', 'check-flags.test', diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 6b4804708d28a..1b033874fc038 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -491,16 +491,18 @@ class Instr(Generic[T]): ... plugins=/test-data/unit/plugins/dyn_class.py [case testDynamicClassPluginNegatives] -# flags: --config-file tmp/mypy.ini +# flags: --new-semantic-analyzer --config-file tmp/mypy.ini from mod import declarative_base, Column, Instr, non_declarative_base Bad1 = non_declarative_base() Bad2 = Bad3 = declarative_base() -class C1(Bad1): ... # E: Invalid base class -class C2(Bad2): ... # E: Invalid base class -class C3(Bad3): ... # E: Invalid base class - +class C1(Bad1): ... # E: Invalid base class \ + # E: Invalid type "__main__.Bad1" +class C2(Bad2): ... # E: Invalid base class \ + # E: Invalid type "__main__.Bad2" +class C3(Bad3): ... # E: Invalid base class \ + # E: Invalid type "__main__.Bad3" [file mod.py] from typing import Generic, TypeVar def declarative_base(): ... diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index f022423b4f0d0..0b094d64e6853 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -393,8 +393,9 @@ class Application: [builtins fixtures/list.pyi] +-- Blocked by #6454 [case testDataclassOrderingWithCustomMethods] -# flags: --python-version 3.6 +# flags: --python-version 3.6 --no-new-semantic-analyzer from dataclasses import dataclass @dataclass(order=True) diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 2a377e85ab79b..08ee171ef610a 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1400,6 +1400,7 @@ class B(A): [out] -> , m + -> -> -> , m.B.__init__ -> From 8cb84146bbecac9ee4d21d800d1fd77930812694 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Mar 2019 16:01:17 +0000 Subject: [PATCH 2/2] Update type variable name in a test --- test-data/unit/check-incremental.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index b8d0fec81b160..4412156c1cd52 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -3864,10 +3864,10 @@ class A: tmp/b.py:3: error: Revealed type is 'def (a: builtins.int) -> a.A' tmp/b.py:4: error: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool' tmp/b.py:5: error: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool' -tmp/b.py:6: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool' -tmp/b.py:7: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool' -tmp/b.py:8: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool' -tmp/b.py:9: error: Revealed type is 'def [T] (self: T`-1, other: T`-1) -> builtins.bool' +tmp/b.py:6: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool' +tmp/b.py:7: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool' +tmp/b.py:8: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool' +tmp/b.py:9: error: Revealed type is 'def [_DT] (self: _DT`-1, other: _DT`-1) -> builtins.bool' tmp/b.py:18: error: Unsupported operand types for < ("A" and "int") tmp/b.py:19: error: Unsupported operand types for <= ("A" and "int") tmp/b.py:20: error: Unsupported operand types for > ("A" and "int")