10000 New semantic analyzer: Fix --disallow-untyped-defs with attrs plugin … · python/mypy@dadae5f · GitHub
[go: up one dir, main page]

Skip to content

Commit dadae5f

Browse files
authored
New semantic analyzer: Fix --disallow-untyped-defs with attrs plugin and forward references (#6724)
The fix essentially mirrors the one for dataclasses. Note I temporary add `final_iteration` to the old analyzer, because plugin files import everything from the old one. The non-trivial part is removing previously generated functions in plugins to make them re-entrant.
1 parent b6afa7f commit dadae5f

File tree

7 files changed

+83
-2
lines changed

7 files changed

+83
-2
lines changed

mypy/plugin.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
import types
4141

42-
from abc import abstractmethod
42+
from abc import abstractmethod, abstractproperty
4343
from typing import Any, Callable, List, Tuple, Optional, NamedTuple, TypeVar, Dict
4444
from mypy_extensions import trait
4545

@@ -263,6 +263,14 @@ def defer(self) -> None:
263263
"""
264264
raise NotImplementedError
265265

266+
@abstractproperty
267+
def final_iteration(self) -> bool:
268+
"""Is this the final iteration of semantic analysis?
269+
270+
Only available with new semantic analyzer.
271+
"""
272+
raise NotImplementedError
273+
266274

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

mypy/plugins/attrs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
210210

211211
attributes = _analyze_class(ctx, auto_attribs, kw_only)
212212

213+
if ctx.api.options.new_semantic_analyzer:
214+
# Check if attribute types are ready.
215+
for attr in attributes:
216+
if info[attr.name].type is None and not ctx.api.final_iteration:
217+
ctx.api.defer()
218+
return
219+
213220
# Save the attributes so that subclasses can reuse them.
214221
ctx.cls.info.metadata['attrs'] = {
215222
'attributes': [attr.serialize() for attr in attributes],

mypy/plugins/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ def add_method(
8888
"""Adds a new method to a class.
8989
"""
9090
info = ctx.cls 8000 .info
91+
92+
# First remove any previously generated methods with the same name
93+
# to avoid clashes and problems in new semantic analyzer.
94+
if name in info.names:
95+
sym = info.names[name]
96+
if sym.plugin_generated and isinstance(sym.node, FuncDef):
97+
ctx.cls.defs.body.remove(sym.node)
98+
9199
self_type = self_type or fill_typevars(info)
92100
function_type = ctx.api.named_type('__builtins__.function')
93101

mypy/semanal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3820,6 +3820,11 @@ def defer(self) -> None:
38203820
assert not self.options.new_semantic_analyzer
38213821
raise NotImplementedError('This is only available with --new-semantic-analyzer')
38223822

3823+
@property
3824+
def final_iteration(self) -> bool:
3825+
assert not self.options.new_semantic_analyzer
3826+
raise NotImplementedError('This is only available with --new-semantic-analyzer')
3827+
38233828

38243829
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:
38253830
if isinstance(sig, CallableType):

test-data/unit/check-attr.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,3 +1023,29 @@ class A(object):
10231023
class B(object):
10241024
x = attr.ib(kw_only=True) # E: kw_only is not supported in Python 2
10251025
[builtins_py2 fixtures/bool.pyi]
1026+
1027+
[case testAttrsDisallowUntypedWorksForward]
1028+
# flags: --disallow-untyped-defs
1029+
import attr
1030+
from typing import List
1031+
1032+
@attr.s
1033+
class B:
1034+
x: C = attr.ib()
1035+
1036+
class C(List[C]):
1037+
pass
1038+
1039+
reveal_type(B) # E: Revealed type is 'def (x: __main__.C) -> __main__.B'
1040+
[builtins fixtures/list.pyi]
1041+
1042+
[case testDisallowUntypedWorksForwardBad]
1043+
# flags: --disallow-untyped-defs
1044+
import attr
1045+
1046+
@attr.s # E: Function is missing a type annotation for one or more arguments
1047+
class B:
1048+
x = attr.ib() # E: Need type annotation for 'x'
1049+
1050+
reveal_type(B) # E: Revealed type is 'def (x: Any) -> __main__.B'
1051+
[builtins fixtures/list.pyi]

test-data/unit/check-dataclasses.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,30 @@ class Foo:
559559
bar: Optional[int] = field(default=None)
560560
[builtins fixtures/list.pyi]
561561
[out]
562+
563+
[case testDisallowUntypedWorksForward]
564+
# flags: --disallow-untyped-defs
565+
from dataclasses import dataclass
566+
from typing import List
567+
568+
@dataclass
569+
class B:
570+
x: C
571+
572+
class C(List[C]):
573+
pass
574+
575+
reveal_type(B) # E: Revealed type is 'def (x: __main__.C) -> __main__.B'
576+
[builtins fixtures/list.pyi]
577+
578+
[case testDisallowUntypedWorksForwardBad]
579+
# flags: --disallow-untyped-defs
580+
from dataclasses import dataclass
581+
582+
@dataclass
583+
class B:
584+
x: Undefined # E: Name 'Undefined' is not defined
585+
y = undefined() # E: Name 'undefined' is not defined
586+
587+
reveal_type(B) # E: Revealed type is 'def (x: Any) -> __main__.B'
588+
[builtins fixtures/list.pyi]

test-data/unit/fixtures/list.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from typing import TypeVar, Generic, Iterable, Iterator, Sequence, overload
55
T = TypeVar('T')
66

77
class object:
8-
def __init__(self): pass
8+
def __init__(self) -> None: pass
99

1010
class type: pass
1111
class ellipsis: pass

0 commit comments

Comments
 (0)
0