Skip to content

Commit 94745f5

Browse files
authored
Fix incremental crash bug caused by NewType in functions (#8607)
1 parent 698ed9a commit 94745f5

File tree

4 files changed

+42
-9
lines changed

4 files changed

+42
-9
lines changed

mypy/semanal_newtype.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,27 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
4242
The logic in this function mostly copies the logic for visit_class_def()
4343
with a single (non-Generic) base.
4444
"""
45-
name, call = self.analyze_newtype_declaration(s)
46-
if name is None or call is None:
45+
var_name, call = self.analyze_newtype_declaration(s)
46+
if var_name is None or call is None:
4747
return False
48+
name = var_name
4849
# OK, now we know this is a NewType. But the base type may be not ready yet,
4950
# add placeholder as we do for ClassDef.
5051

52+
if self.api.is_func_scope():
53+
name += '@' + str(s.line)
5154
fullname = self.api.qualified_name(name)
55+
5256
if (not call.analyzed or
5357
isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info):
5458
# Start from labeling this as a future class, as we do for normal ClassDefs.
5559
placeholder = PlaceholderNode(fullname, s, s.line, becomes_typeinfo=True)
56-
self.api.add_symbol(name, placeholder, s, can_defer=False)
60+
self.api.add_symbol(var_name, placeholder, s, can_defer=False)
5761

58-
old_type, should_defer = self.check_newtype_args(name, call, s)
62+
old_type, should_defer = self.check_newtype_args(var_name, call, s)
5963
old_type = get_proper_type(old_type)
6064
if not call.analyzed:
61-
call.analyzed = NewTypeExpr(name, old_type, line=call.line, column=call.column)
65+
call.analyzed = NewTypeExpr(var_name, old_type, line=call.line, column=call.column)
6266
if old_type is None:
6367
if should_defer:
6468
# Base type is not ready.
@@ -98,7 +102,9 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
98102
call.analyzed.info = newtype_class_info
99103
else:
100104
call.analyzed.info.bases = newtype_class_info.bases
101-
self.api.add_symbol(name, call.analyzed.info, s)
105+
self.api.add_symbol(var_name, call.analyzed.info, s)
106+
if self.api.is_func_scope():
107+
self.api.add_symbol_skip_local(name, call.analyzed.info)
102108
newtype_class_info.line = s.line
103109
return True
104110

@@ -191,7 +197,7 @@ def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance)
191197
name=name)
192198
init_func = FuncDef('__init__', args, Block([]), typ=signature)
193199
init_func.info = info
194-
init_func._fullname = self.api.qualified_name(name) + '.__init__'
200+
init_func._fullname = info.fullname + '.__init__'
195201
info.names['__init__'] = SymbolTableNode(MDEF, init_func)
196202

197203
return info

mypy/semanal_shared.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ def qualified_name(self, n: str) -> str:
162162
def is_typeshed_stub_file(self) -> bool:
163163
raise NotImplementedError
164164

165+
@abstractmethod
166+
def is_func_scope(self) -> bool:
167+
raise NotImplementedError
168+
165169

166170
def create_indirect_imported_name(file_node: MypyFile,
167171
module: str,

test-data/unit/check-incremental.test

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1896,6 +1896,28 @@ main:1: error: Module 'tdcrash' has no attribute 'nope'
18961896
[out2]
18971897
main:1: error: Module 'tdcrash' has no attribute 'nope'
18981898

1899+
[case testIncrementalNewTypeInMethod]
1900+
from ntcrash import nope
1901+
[file ntcrash.py]
1902+
from mypy_extensions import TypedDict
1903+
from typing import NewType, NamedTuple
1904+
class C:
1905+
def f(self) -> None:
1906+
X = NewType('X', int)
1907+
A = TypedDict('A', {'x': X, 'y': int})
1908+
B = NamedTuple('B', [('x', X)])
1909+
1910+
def f() -> None:
1911+
X = NewType('X', int)
1912+
A = TypedDict('A', {'x': X, 'y': int})
1913+
B = NamedTuple('B', [('x', X)])
1914+
1915+
[builtins fixtures/dict.pyi]
1916+
[out1]
1917+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1918+
[out2]
1919+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1920+
18991921
[case testIncrementalInnerClassAttrInMethod]
19001922
import crash
19011923
nonexisting
@@ -2274,6 +2296,7 @@ tmp/c.py:1: error: Module 'd' has no attribute 'x'
22742296
[out]
22752297
[out2]
22762298
mypy: can't read file 'tmp/nonexistent.py': No such file or directory
2299+
-- '
22772300

22782301
[case testSerializeAbstractPropertyIncremental]
22792302
from abc import abstractmethod
@@ -2484,7 +2507,7 @@ A = Dict[str, int]
24842507
[out]
24852508

24862509
-- Some crazy self-referential named tuples, types dicts, and aliases
2487-
-- to be sure that everything can be _serialized_ (i.e. ForwardRef's are removed).
2510+
-- to be sure that everything can be _serialized_ (i.e. ForwardRefs are removed).
24882511
-- For this reason errors are silenced (tests with # type: ignore have equivalents in other files)
24892512

24902513
[case testForwardTypeAliasInBase1]

test-data/unit/check-newtype.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def func() -> None:
194194
A = NewType('A', str)
195195
B = NewType('B', str)
196196

197-
a = A(3) # E: Argument 1 to "A" has incompatible type "int"; expected "str"
197+
a = A(3) # E: Argument 1 to "A@6" has incompatible type "int"; expected "str"
198198
a = A('xyz')
199199
b = B('xyz')
200200

0 commit comments

Comments
 (0)