diff --git a/mypy/nodes.py b/mypy/nodes.py index 992fd8a59f60..0571788bf002 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -138,11 +138,17 @@ def get_column(self) -> int: 'builtins.frozenset': 'typing.FrozenSet', } # type: Final -nongen_builtins = {'builtins.tuple': 'typing.Tuple', - 'builtins.enumerate': ''} # type: Final -nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) +_nongen_builtins = {'builtins.tuple': 'typing.Tuple', + 'builtins.enumerate': ''} # type: Final +_nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) # Drop OrderedDict from this for backward compatibility -del nongen_builtins['collections.OrderedDict'] +del _nongen_builtins['collections.OrderedDict'] + + +def get_nongen_builtins(python_version: Tuple[int, int]) -> Dict[str, str]: + # After 3.9 with pep585 generic builtins are allowed. + return _nongen_builtins if python_version < (3, 9) else {} + RUNTIME_PROTOCOL_DECOS = ('typing.runtime_checkable', 'typing_extensions.runtime', diff --git a/mypy/semanal.py b/mypy/semanal.py index cf02e967242c..840c5effdb2e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -73,7 +73,7 @@ YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, TempNode, OverloadPart, PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT, - nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, + get_nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_source_versions, EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, ParamSpecExpr @@ -461,7 +461,7 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: target = self.named_type_or_none(target_name, []) assert target is not None # Transform List to List[Any], etc. - fix_instance_types(target, self.fail, self.note) + fix_instance_types(target, self.fail, self.note, self.options.python_version) alias_node = TypeAlias(target, alias, line=-1, column=-1, # there is no context no_args=True, normalized=True) @@ -2585,7 +2585,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # if the expected number of arguments is non-zero, so that aliases like A = List work. # However, eagerly expanding aliases like Text = str is a nice performance optimization. no_args = isinstance(res, Instance) and not res.args # type: ignore - fix_instance_types(res, self.fail, self.note) + fix_instance_types(res, self.fail, self.note, self.options.python_version) alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, alias_tvars=alias_tvars, no_args=no_args) if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` @@ -3803,12 +3803,13 @@ def analyze_type_application(self, expr: IndexExpr) -> None: if isinstance(target, Instance): name = target.type.fullname if (alias.no_args and # this avoids bogus errors for already reported aliases - name in nongen_builtins and not alias.normalized): + name in get_nongen_builtins(self.options.python_version) and + not alias.normalized): self.fail(no_subscript_builtin_alias(name, propose_alt=False), expr) # ...or directly. else: n = self.lookup_type_node(base) - if n and n.fullname in nongen_builtins: + if n and n.fullname in get_nongen_builtins(self.options.python_version): self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr) def analyze_type_application_args(self, expr: IndexExpr) -> Optional[List[Type]]: diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index f266a474a59a..5dd8c3b286a9 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -65,7 +65,6 @@ 'check-selftype.test', 'check-python2.test', 'check-columns.test', - 'check-future.test', 'check-functions.test', 'check-tuples.test', 'check-expressions.test', @@ -91,6 +90,7 @@ 'check-errorcodes.test', 'check-annotated.test', 'check-parameter-specification.test', + 'check-generic-alias.test', ] # Tests that use Python 3.8-only AST features (like expression-scoped ignores): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7a7408d351e1..5eb6f0576c0d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -21,7 +21,7 @@ from mypy.nodes import ( TypeInfo, Context, SymbolTableNode, Var, Expression, - nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED, + get_nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED, ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr, TypeVarLikeExpr, ParamSpecExpr, TypeAlias, PlaceholderNode, SYMBOL_FUNCBASE_TYPES, Decorator, MypyFile ) @@ -94,6 +94,8 @@ def analyze_type_alias(node: Expression, def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str: msg = '"{}" is not subscriptable'.format(name.split('.')[-1]) + # This should never be called if the python_version is 3.9 or newer + nongen_builtins = get_nongen_builtins((3, 8)) replacement = nongen_builtins[name] if replacement and propose_alt: msg += ', use "{}" instead'.format(replacement) @@ -194,7 +196,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) hook = self.plugin.get_type_analyze_hook(fullname) if hook is not None: return hook(AnalyzeTypeContext(t, t, self)) - if (fullname in nongen_builtins + if (fullname in get_nongen_builtins(self.options.python_version) and t.args and not self.allow_unnormalized and not self.api.is_future_flag_set("annotations")): @@ -241,6 +243,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) self.fail, self.note, disallow_any=disallow_any, + python_version=self.options.python_version, use_generic_error=True, unexpanded_type=t) return res @@ -272,7 +275,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt self.fail("Final can be only used as an outermost qualifier" " in a variable annotation", t) return AnyType(TypeOfAny.from_error) - elif fullname == 'typing.Tuple': + elif (fullname == 'typing.Tuple' or + (fullname == 'builtins.tuple' and (self.options.python_version >= (3, 9) or + self.api.is_future_flag_set('annotations')))): # Tuple is special because it is involved in builtin import cycle # and may be not ready when used. sym = self.api.lookup_fully_qualified_or_none('builtins.tuple') @@ -305,7 +310,8 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt elif fullname == 'typing.Callable': return self.analyze_callable_type(t) elif (fullname == 'typing.Type' or - (fullname == 'builtins.type' and self.api.is_future_flag_set('annotations'))): + (fullname == 'builtins.type' and (self.options.python_version >= (3, 9) or + self.api.is_future_flag_set('annotations')))): if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) @@ -342,7 +348,8 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt def get_omitted_any(self, typ: Type, fullname: Optional[str] = None) -> AnyType: disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics - return get_omitted_any(disallow_any, self.fail, self.note, typ, fullname) + return get_omitted_any(disallow_any, self.fail, self.note, typ, + self.options.python_version, fullname) def analyze_type_with_type_info( self, info: TypeInfo, args: Sequence[Type], ctx: Context) -> Type: @@ -364,7 +371,8 @@ def analyze_type_with_type_info( if len(instance.args) != len(info.type_vars) and not self.defining_alias: fix_instance(instance, self.fail, self.note, disallow_any=self.options.disallow_any_generics and - not self.is_typeshed_stub) + not self.is_typeshed_stub, + python_version=self.options.python_version) tup = info.tuple_type if tup is not None: @@ -970,9 +978,11 @@ def tuple_type(self, items: List[Type]) -> TupleType: def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: MsgCallback, - orig_type: Type, fullname: Optional[str] = None, + orig_type: Type, python_version: Tuple[int, int], + fullname: Optional[str] = None, unexpanded_type: Optional[Type] = None) -> AnyType: if disallow_any: + nongen_builtins = get_nongen_builtins(python_version) if fullname in nongen_builtins: typ = orig_type # We use a dedicated error message for builtin generics (as the most common case). @@ -1010,7 +1020,8 @@ def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: MsgCallback, def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, - disallow_any: bool, use_generic_error: bool = False, + disallow_any: bool, python_version: Tuple[int, int], + use_generic_error: bool = False, unexpanded_type: Optional[Type] = None,) -> None: """Fix a malformed instance by replacing all type arguments with Any. @@ -1021,7 +1032,8 @@ def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, fullname = None # type: Optional[str] else: fullname = t.type.fullname - any_type = get_omitted_any(disallow_any, fail, note, t, fullname, unexpanded_type) + any_type = get_omitted_any(disallow_any, fail, note, t, python_version, fullname, + unexpanded_type) t.args = (any_type,) * len(t.type.type_vars) return # Invalid number of type parameters. @@ -1280,21 +1292,26 @@ def make_optional_type(t: Type) -> Type: return UnionType([t, NoneType()], t.line, t.column) -def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback) -> None: +def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, + python_version: Tuple[int, int]) -> None: """Recursively fix all instance types (type argument count) in a given type. For example 'Union[Dict, List[str, int]]' will be transformed into 'Union[Dict[Any, Any], List[Any]]' in place. """ - t.accept(InstanceFixer(fail, note)) + t.accept(InstanceFixer(fail, note, python_version)) class InstanceFixer(TypeTraverserVisitor): - def __init__(self, fail: MsgCallback, note: MsgCallback) -> None: + def __init__( + self, fail: MsgCallback, note: MsgCallback, python_version: Tuple[int, int] + ) -> None: self.fail = fail self.note = note + self.python_version = python_version def visit_instance(self, typ: Instance) -> None: super().visit_instance(typ) if len(typ.args) != len(typ.type.type_vars): - fix_instance(typ, self.fail, self.note, disallow_any=False, use_generic_error=True) + fix_instance(typ, self.fail, self.note, disallow_any=False, + python_version=self.python_version, use_generic_error=True) diff --git a/test-data/unit/check-future.test b/test-data/unit/check-future.test deleted file mode 100644 index 9ccf4eaa3dd2..000000000000 --- a/test-data/unit/check-future.test +++ /dev/null @@ -1,24 +0,0 @@ --- 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/check-generic-alias.test b/test-data/unit/check-generic-alias.test new file mode 100644 index 000000000000..e7a1354d85cb --- /dev/null +++ b/test-data/unit/check-generic-alias.test @@ -0,0 +1,241 @@ +-- Test cases for generic aliases + +[case testGenericBuiltinWarning] +# flags: --python-version 3.7 +t1: list +t2: list[int] # E: "list" is not subscriptable, use "typing.List" instead +t3: list[str] # E: "list" is not subscriptable, use "typing.List" instead + +t4: tuple +t5: tuple[int] # E: "tuple" is not subscriptable, use "typing.Tuple" instead +t6: tuple[int, str] # E: "tuple" is not subscriptable, use "typing.Tuple" instead +t7: tuple[int, ...] # E: Unexpected '...' \ + # E: "tuple" is not subscriptable, use "typing.Tuple" instead + +t8: dict = {} +t9: dict[int, str] # E: "dict" is not subscriptable, use "typing.Dict" instead + +t10: type +t11: type[int] # E: "type" expects no type arguments, but 1 given +[builtins fixtures/dict.pyi] + + +[case testGenericBuiltinSetWarning] +# flags: --python-version 3.7 +t1: set +t2: set[int] # E: "set" is not subscriptable, use "typing.Set" instead +[builtins fixtures/set.pyi] + + +[case testGenericCollectionsWarning] +# flags: --python-version 3.7 +import collections + +t01: collections.deque +t02: collections.deque[int] # E: "deque" is not subscriptable, use "typing.Deque" instead +t03: collections.defaultdict +t04: collections.defaultdict[int, str] # E: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead +t05: collections.OrderedDict +t06: collections.OrderedDict[int, str] +t07: collections.Counter +t08: collections.Counter[int] # E: "Counter" is not subscriptable, use "typing.Counter" instead +t09: collections.ChainMap +t10: collections.ChainMap[int, str] # E: "ChainMap" is not subscriptable, use "typing.ChainMap" instead + + +[case testGenericBuiltinFutureAnnotations] +# flags: --python-version 3.7 +from __future__ import annotations +t1: list +t2: list[int] +t3: list[str] + +t4: tuple +t5: tuple[int] +t6: tuple[int, str] +t7: tuple[int, ...] + +t8: dict = {} +t9: dict[int, str] + +t10: type +t11: type[int] +[builtins fixtures/dict.pyi] + + +[case testGenericCollectionsFutureAnnotations] +# flags: --python-version 3.7 +from __future__ import annotations +import collections + +t01: collections.deque +t02: collections.deque[int] +t03: collections.defaultdict +t04: collections.defaultdict[int, str] +t05: collections.OrderedDict +t06: collections.OrderedDict[int, str] +t07: collections.Counter +t08: collections.Counter[int] +t09: collections.ChainMap +t10: collections.ChainMap[int, str] +[builtins fixtures/tuple.pyi] + + +[case testGenericAliasBuiltinsReveal] +# flags: --python-version 3.9 +t1: list +t2: list[int] +t3: list[str] + +t4: tuple +t5: tuple[int] +t6: tuple[int, str] +t7: tuple[int, ...] + +t8: dict = {} +t9: dict[int, str] + +t10: type +t11: type[int] + +reveal_type(t1) # N: Revealed type is 'builtins.list[Any]' +reveal_type(t2) # N: Revealed type is 'builtins.list[builtins.int]' +reveal_type(t3) # N: Revealed type is 'builtins.list[builtins.str]' +reveal_type(t4) # N: Revealed type is 'builtins.tuple[Any]' +# TODO: ideally these would reveal builtins.tuple +reveal_type(t5) # N: Revealed type is 'Tuple[builtins.int]' +reveal_type(t6) # N: Revealed type is 'Tuple[builtins.int, builtins.str]' +# TODO: this is incorrect, see #9522 +reveal_type(t7) # N: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(t8) # N: Revealed type is 'builtins.dict[Any, Any]' +reveal_type(t9) # N: Revealed type is 'builtins.dict[builtins.int, builtins.str]' +reveal_type(t10) # N: Revealed type is 'builtins.type' +reveal_type(t11) # N: Revealed type is 'Type[builtins.int]' +[builtins fixtures/dict.pyi] + + +[case testGenericAliasBuiltinsSetReveal] +# flags: --python-version 3.9 +t1: set +t2: set[int] +t3: set[str] + +reveal_type(t1) # N: Revealed type is 'builtins.set[Any]' +reveal_type(t2) # N: Revealed type is 'builtins.set[builtins.int]' +reveal_type(t3) # N: Revealed type is 'builtins.set[builtins.str]' +[builtins fixtures/set.pyi] + + +[case testGenericAliasCollectionsReveal] +# flags: --python-version 3.9 +import collections + +t1: collections.deque[int] +t2: collections.defaultdict[int, str] +t3: collections.OrderedDict[int, str] +t4: collections.Counter[int] +t5: collections.ChainMap[int, str] + +reveal_type(t1) # N: Revealed type is 'collections.deque[builtins.int]' +reveal_type(t2) # N: Revealed type is 'collections.defaultdict[builtins.int, builtins.str]' +reveal_type(t3) # N: Revealed type is 'collections.OrderedDict[builtins.int, builtins.str]' +reveal_type(t4) # N: Revealed type is 'collections.Counter[builtins.int]' +reveal_type(t5) # N: Revealed type is 'collections.ChainMap[builtins.int, builtins.str]' +[builtins fixtures/tuple.pyi] + + +[case testGenericAliasCollectionsABCReveal] +# flags: --python-version 3.9 +import collections.abc + +t01: collections.abc.Awaitable[int] +t02: collections.abc.Coroutine[str, int, float] +t03: collections.abc.AsyncIterable[int] +t04: collections.abc.AsyncIterator[int] +t05: collections.abc.AsyncGenerator[int, float] +t06: collections.abc.Iterable[int] +t07: collections.abc.Iterator[int] +t08: collections.abc.Generator[int, float, str] +t09: collections.abc.Reversible[int] +t10: collections.abc.Container[int] +t11: collections.abc.Collection[int] +t12: collections.abc.Callable[[int], float] +t13: collections.abc.Set[int] +t14: collections.abc.MutableSet[int] +t15: collections.abc.Mapping[int, str] +t16: collections.abc.MutableMapping[int, str] +t17: collections.abc.Sequence[int] +t18: collections.abc.MutableSequence[int] +t19: collections.abc.ByteString +t20: collections.abc.MappingView[int, int] +t21: collections.abc.KeysView[int] +t22: collections.abc.ItemsView[int, str] +t23: collections.abc.ValuesView[str] + +# TODO: these currently reveal the classes from typing, see #7907 +# reveal_type(t01) # Nx Revealed type is 'collections.abc.Awaitable[builtins.int]' +# reveal_type(t02) # Nx Revealed type is 'collections.abc.Coroutine[builtins.str, builtins.int, builtins.float]' +# reveal_type(t03) # Nx Revealed type is 'collections.abc.AsyncIterable[builtins.int]' +# reveal_type(t04) # Nx Revealed type is 'collections.abc.AsyncIterator[builtins.int]' +# reveal_type(t05) # Nx Revealed type is 'collections.abc.AsyncGenerator[builtins.int, builtins.float]' +# reveal_type(t06) # Nx Revealed type is 'collections.abc.Iterable[builtins.int]' +# reveal_type(t07) # Nx Revealed type is 'collections.abc.Iterator[builtins.int]' +# reveal_type(t08) # Nx Revealed type is 'collections.abc.Generator[builtins.int, builtins.float, builtins.str]' +# reveal_type(t09) # Nx Revealed type is 'collections.abc.Reversible[builtins.int]' +# reveal_type(t10) # Nx Revealed type is 'collections.abc.Container[builtins.int]' +# reveal_type(t11) # Nx Revealed type is 'collections.abc.Collection[builtins.int]' +# reveal_type(t12) # Nx Revealed type is 'collections.abc.Callable[[builtins.int], builtins.float]' +# reveal_type(t13) # Nx Revealed type is 'collections.abc.Set[builtins.int]' +# reveal_type(t14) # Nx Revealed type is 'collections.abc.MutableSet[builtins.int]' +# reveal_type(t15) # Nx Revealed type is 'collections.abc.Mapping[builtins.int, builtins.str]' +# reveal_type(t16) # Nx Revealed type is 'collections.abc.MutableMapping[builtins.int, builtins.str]' +# reveal_type(t17) # Nx Revealed type is 'collections.abc.Sequence[builtins.int]' +# reveal_type(t18) # Nx Revealed type is 'collections.abc.MutableSequence[builtins.int]' +# reveal_type(t19) # Nx Revealed type is 'collections.abc.ByteString' +# reveal_type(t20) # Nx Revealed type is 'collections.abc.MappingView[builtins.int, builtins.int]' +# reveal_type(t21) # Nx Revealed type is 'collections.abc.KeysView[builtins.int]' +# reveal_type(t22) # Nx Revealed type is 'collections.abc.ItemsView[builtins.int, builtins.str]' +# reveal_type(t23) # Nx Revealed type is 'collections.abc.ValuesView[builtins.str]' +[builtins fixtures/tuple.pyi] + + +[case testGenericAliasImportRe] +# flags: --python-version 3.9 +from re import Pattern, Match +t1: Pattern[str] +t2: Match[str] +[builtins fixtures/tuple.pyi] + + +[case testGenericBuiltinTupleTyping] +# flags: --python-version 3.6 +from typing import Tuple + +t01: Tuple = () +t02: Tuple[int] = (1, ) +t03: Tuple[int, str] = (1, 'a') +t04: Tuple[int, int] = (1, 2) +t05: Tuple[int, int, int] = (1, 2, 3) +t06: Tuple[int, ...] +t07: Tuple[int, ...] = (1,) +t08: Tuple[int, ...] = (1, 2) +t09: Tuple[int, ...] = (1, 2, 3) +[builtins fixtures/tuple.pyi] + + +[case testGenericBuiltinTuple] +# flags: --python-version 3.9 + +t01: tuple = () +t02: tuple[int] = (1, ) +t03: tuple[int, str] = (1, 'a') +t04: tuple[int, int] = (1, 2) +t05: tuple[int, int, int] = (1, 2, 3) +t06: tuple[int, ...] +t07: tuple[int, ...] = (1,) +t08: tuple[int, ...] = (1, 2) +t09: tuple[int, ...] = (1, 2, 3) + +from typing import Tuple +t10: Tuple[int, ...] = t09 +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 541f63d10039..60c2e64d084f 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -663,6 +663,7 @@ a.__pow__() # E: All overload variants of "__pow__" of "int" require at least on # cmd: mypy m.py [file mypy.ini] \[mypy] +python_version=3.6 \[mypy-m] disallow_any_generics = True diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index e3eaf8a00ff3..c401b57f8db9 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -854,6 +854,7 @@ _program.py:19: error: Dict entry 0 has incompatible type "str": "List[ _program.py:23: error: Invalid index type "str" for "MyDDict[Dict[_KT, _VT]]"; expected type "int" [case testNoSubcriptionOfStdlibCollections] +# flags: --python-version 3.6 import collections from collections import Counter from typing import TypeVar @@ -870,11 +871,11 @@ d[0] = 1 def f(d: collections.defaultdict[int, str]) -> None: ... [out] -_program.py:5: error: "defaultdict" is not subscriptable -_program.py:6: error: "Counter" is not subscriptable -_program.py:9: error: "defaultdict" is not subscriptable -_program.py:12: error: Invalid index type "int" for "defaultdict[str, int]"; expected type "str" -_program.py:14: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead +_program.py:6: error: "defaultdict" is not subscriptable +_program.py:7: error: "Counter" is not subscriptable +_program.py:10: error: "defaultdict" is not subscriptable +_program.py:13: error: Invalid index type "int" for "defaultdict[str, int]"; expected type "str" +_program.py:15: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead [case testCollectionsAliases] import typing as t