diff --git a/mypy/semanal.py b/mypy/semanal.py index 95890587b21d..3fe010716f9a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -123,6 +123,20 @@ T = TypeVar('T') +FUTURE_IMPORTS = { + '__future__.nested_scopes': 'nested_scopes', + '__future__.generators': 'generators', + '__future__.division': 'division', + '__future__.absolute_import': 'absolute_import', + '__future__.with_statement': 'with_statement', + '__future__.print_function': 'print_function', + '__future__.unicode_literals': 'unicode_literals', + '__future__.barry_as_FLUFL': 'barry_as_FLUFL', + '__future__.generator_stop': 'generator_stop', + '__future__.annotations': 'annotations', +} # type: Final + + # Special cased built-in classes that are needed for basic functionality and need to be # available very early on. CORE_BUILTIN_CLASSES = ['object', 'bool', 'function'] # type: Final @@ -199,6 +213,7 @@ class SemanticAnalyzer(NodeVisitor[None], errors = None # type: Errors # Keeps track of generated errors plugin = None # type: Plugin # Mypy plugin for special casing of library features statement = None # type: Optional[Statement] # Statement/definition being analyzed + future_import_flags = None # type: Set[str] def __init__(self, modules: Dict[str, MypyFile], @@ -254,6 +269,8 @@ def __init__(self, # current SCC or top-level function. self.deferral_debug_context = [] # type: List[Tuple[str, int]] + self.future_import_flags = set() # type: Set[str] + # mypyc doesn't properly handle implementing an abstractproperty # with a regular attribute so we make them properties @property @@ -1694,6 +1711,7 @@ def visit_import_from(self, imp: ImportFrom) -> None: module = self.modules.get(module_id) for id, as_id in imp.names: fullname = module_id + '.' + id + self.set_future_import_flags(fullname) if module is None: node = None elif module_id == self.cur_mod_id and fullname in self.modules: @@ -1853,6 +1871,8 @@ def visit_import_all(self, i: ImportAll) -> None: # namespace is incomplete. self.mark_incomplete('*', i) for name, node in m.names.items(): + fullname = i_id + '.' + name + self.set_future_import_flags(fullname) if node is None: continue # if '__all__' exists, all nodes not included have had module_public set to @@ -4835,6 +4855,13 @@ def parse_bool(self, expr: Expression) -> Optional[bool]: return False return None + def set_future_import_flags(self, module_name: str) -> None: + if module_name in FUTURE_IMPORTS: + self.future_import_flags.add(FUTURE_IMPORTS[module_name]) + + def is_future_flag_set(self, flag: str) -> bool: + return flag in self.future_import_flags + class HasPlaceholders(TypeQuery[bool]): def __init__(self) -> None: diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index ced37c600f14..e96f32af51bf 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -73,6 +73,11 @@ def final_iteration(self) -> bool: """Is this the final iteration of semantic analysis?""" raise NotImplementedError + @abstractmethod + def is_future_flag_set(self, flag: str) -> bool: + """Is the specific __future__ feature imported""" + raise NotImplementedError + @trait class SemanticAnalyzerInterface(SemanticAnalyzerCoreInterface): diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 49a85861b6e3..515f27c9765c 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -65,6 +65,7 @@ 'check-selftype.test', 'check-python2.test', 'check-columns.test', + 'check-future.test', 'check-functions.test', 'check-tuples.test', 'check-expressions.test', diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 183a9a792c91..e7c7fb22c236 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -196,7 +196,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return hook(AnalyzeTypeContext(t, t, self)) if (fullname in nongen_builtins and t.args and - not self.allow_unnormalized): + not self.allow_unnormalized and + not self.api.is_future_flag_set("annotations")): self.fail(no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t) tvar_def = self.tvar_scope.get_binding(sym) @@ -291,12 +292,14 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return make_optional_type(item) elif fullname == 'typing.Callable': return self.analyze_callable_type(t) - elif fullname == 'typing.Type': + elif (fullname == 'typing.Type' or + (fullname == 'builtins.type' and self.api.is_future_flag_set('annotations'))): if len(t.args) == 0: any_type = self.get_omitted_any(t) return TypeType(any_type, line=t.line, column=t.column) + type_str = 'Type[...]' if fullname == 'typing.Type' else 'type[...]' if len(t.args) != 1: - self.fail('Type[...] must have exactly one type argument', t) + self.fail(type_str + ' must have exactly one type argument', t) item = self.anal_type(t.args[0]) return TypeType.make_normalized(item, line=t.line) elif fullname == 'typing.ClassVar': diff --git a/test-data/unit/check-future.test b/test-data/unit/check-future.test new file mode 100644 index 000000000000..9ccf4eaa3dd2 --- /dev/null +++ b/test-data/unit/check-future.test @@ -0,0 +1,24 @@ +-- Test cases for __future__ imports + +[case testFutureAnnotationsImportCollections] +# flags: --python-version 3.7 +from __future__ import annotations +from collections import defaultdict, ChainMap, Counter, deque + +t1: defaultdict[int, int] +t2: ChainMap[int, int] +t3: Counter[int] +t4: deque[int] + +[builtins fixtures/tuple.pyi] + +[case testFutureAnnotationsImportBuiltIns] +# flags: --python-version 3.7 +from __future__ import annotations + +t1: type[int] +t2: list[int] +t3: dict[int, int] +t4: tuple[int, str, int] + +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/lib-stub/collections.pyi b/test-data/unit/lib-stub/collections.pyi index c5b5ef0504e6..71f797e565e8 100644 --- a/test-data/unit/lib-stub/collections.pyi +++ b/test-data/unit/lib-stub/collections.pyi @@ -1,4 +1,4 @@ -from typing import Any, Iterable, Union, Optional, Dict, TypeVar, overload, Optional, Callable +from typing import Any, Iterable, Union, Optional, Dict, TypeVar, overload, Optional, Callable, Sized def namedtuple( typename: str, @@ -17,3 +17,9 @@ class OrderedDict(Dict[KT, VT]): ... class defaultdict(Dict[KT, VT]): def __init__(self, default_factory: Optional[Callable[[], VT]]) -> None: ... + +class Counter(Dict[KT, int], Generic[KT]): ... + +class deque(Sized, Iterable[KT], Reversible[KT], Generic[KT]): ... + +class ChainMap(MutableMapping[KT, VT], Generic[KT, VT]): ... diff --git a/test.py b/test.py deleted file mode 100644 index e69de29bb2d1..000000000000