Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions mypy/semanal_newtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,27 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
The logic in this function mostly copies the logic for visit_class_def()
with a single (non-Generic) base.
"""
name, call = self.analyze_newtype_declaration(s)
if name is None or call is None:
var_name, call = self.analyze_newtype_declaration(s)
if var_name is None or call is None:
return False
name = var_name
# OK, now we know this is a NewType. But the base type may be not ready yet,
# add placeholder as we do for ClassDef.

if self.api.is_func_scope():
name += '@' + str(s.line)
fullname = self.api.qualified_name(name)

if (not call.analyzed or
isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info):
# Start from labeling this as a future class, as we do for normal ClassDefs.
placeholder = PlaceholderNode(fullname, s, s.line, becomes_typeinfo=True)
self.api.add_symbol(name, placeholder, s, can_defer=False)
self.api.add_symbol(var_name, placeholder, s, can_defer=False)

old_type, should_defer = self.check_newtype_args(name, call, s)
old_type, should_defer = self.check_newtype_args(var_name, call, s)
old_type = get_proper_type(old_type)
if not call.analyzed:
call.analyzed = NewTypeExpr(name, old_type, line=call.line, column=call.column)
call.analyzed = NewTypeExpr(var_name, old_type, line=call.line, column=call.column)
if old_type is None:
if should_defer:
# Base type is not ready.
Expand Down Expand Up @@ -98,7 +102,9 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
call.analyzed.info = newtype_class_info
else:
call.analyzed.info.bases = newtype_class_info.bases
self.api.add_symbol(name, call.analyzed.info, s)
self.api.add_symbol(var_name, call.analyzed.info, s)
if self.api.is_func_scope():
self.api.add_symbol_skip_local(name, call.analyzed.info)
newtype_class_info.line = s.line
return True

Expand Down Expand Up @@ -191,7 +197,7 @@ def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance)
name=name)
init_func = FuncDef('__init__', args, Block([]), typ=signature)
init_func.info = info
init_func._fullname = self.api.qualified_name(name) + '.__init__'
init_func._fullname = info.fullname + '.__init__'
info.names['__init__'] = SymbolTableNode(MDEF, init_func)

return info
Expand Down
4 changes: 4 additions & 0 deletions mypy/semanal_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ def qualified_name(self, n: str) -> str:
def is_typeshed_stub_file(self) -> bool:
raise NotImplementedError

@abstractmethod
def is_func_scope(self) -> bool:
raise NotImplementedError


def create_indirect_imported_name(file_node: MypyFile,
module: str,
Expand Down
25 changes: 24 additions & 1 deletion test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,28 @@ main:1: error: Module 'tdcrash' has no attribute 'nope'
[out2]
main:1: error: Module 'tdcrash' has no attribute 'nope'

[case testIncrementalNewTypeInMethod]
from ntcrash import nope
[file ntcrash.py]
from mypy_extensions import TypedDict
from typing import NewType, NamedTuple
class C:
def f(self) -> None:
X = NewType('X', int)
A = TypedDict('A', {'x': X, 'y': int})
B = NamedTuple('B', [('x', X)])

def f() -> None:
X = NewType('X', int)
A = TypedDict('A', {'x': X, 'y': int})
B = NamedTuple('B', [('x', X)])

[builtins fixtures/dict.pyi]
[out1]
main:1: error: Module 'ntcrash' has no attribute 'nope'
[out2]
main:1: error: Module 'ntcrash' has no attribute 'nope'

[case testIncrementalInnerClassAttrInMethod]
import crash
nonexisting
Expand Down Expand Up @@ -2274,6 +2296,7 @@ tmp/c.py:1: error: Module 'd' has no attribute 'x'
[out]
[out2]
mypy: can't read file 'tmp/nonexistent.py': No such file or directory
-- '

[case testSerializeAbstractPropertyIncremental]
from abc import abstractmethod
Expand Down Expand Up @@ -2484,7 +2507,7 @@ A = Dict[str, int]
[out]

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

[case testForwardTypeAliasInBase1]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-newtype.test
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def func() -> None:
A = NewType('A', str)
B = NewType('B', str)

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

Expand Down