Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement PEP 585 Generic Builtins (Partially solves #7907) #9564

Merged
merged 28 commits into from Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
271e387
Allows builtins to be generic aliases (PEP 585)
AllanDaemon Oct 9, 2020
3472679
fix test testNoSubcriptionOfStdlibCollections
AllanDaemon Oct 9, 2020
1299956
fix test testDisallowAnyGenericsBuiltinCollections
AllanDaemon Oct 9, 2020
ca5c841
Revert "Allows builtins to be generic aliases (PEP 585)"
AllanDaemon Oct 9, 2020
dcfedeb
create get_nongen_builtins() to access nongen_builtins depending on p…
AllanDaemon Oct 9, 2020
a9d0996
implements pep585 accepting generic builtins
AllanDaemon Oct 9, 2020
40b5aed
fix code style to mypy guidelines
AllanDaemon Oct 14, 2020
d4492ae
Added testcases for pep585ga
cdce8p Oct 17, 2020
14f2732
Allows builtins to be generic aliases (PEP 585)
AllanDaemon Oct 24, 2020
6e9341c
fix test testNoSubcriptionOfStdlibCollections
AllanDaemon Oct 24, 2020
f737ef7
fix test testDisallowAnyGenericsBuiltinCollections
AllanDaemon Oct 24, 2020
8d53899
Revert "Allows builtins to be generic aliases (PEP 585)"
AllanDaemon Oct 24, 2020
8b7eb7e
create get_nongen_builtins() to access nongen_builtins depending on p…
AllanDaemon Oct 24, 2020
6b9b798
implements pep585 accepting generic builtins
AllanDaemon Oct 24, 2020
40b2da3
fix code style to mypy guidelines
AllanDaemon Oct 24, 2020
8a17cf9
Merge branch 'pep585ga' into pep585-tests
AllanDaemon Oct 24, 2020
8ca6130
Merge pull request #1 from cdce8p/pep585-tests
AllanDaemon Oct 24, 2020
90a5192
Update test-data/unit/check-generic-alias.test
AllanDaemon Oct 25, 2020
8dbd6ac
@hauntsaninja suggestion
AllanDaemon Oct 25, 2020
779dffd
add tests cases for PEP 585 generic builtins
AllanDaemon Oct 25, 2020
6d58555
add failing tests for PEP 585 to be investigated
AllanDaemon Oct 25, 2020
c9ac0ca
fix `tuple[Any, ...]` case
AllanDaemon Oct 25, 2020
eca2a13
minor test impr. for tuple
AllanDaemon Oct 25, 2020
55dcbb5
Update mypy/typeanal.py
AllanDaemon Oct 25, 2020
c0d0989
disable tests failing due reveal_type()
AllanDaemon Oct 25, 2020
ab0e4cd
fix style to pep8
AllanDaemon Oct 25, 2020
a81abc8
Update test-data/unit/check-generic-alias.test
AllanDaemon Oct 29, 2020
0edf123
collate future annotations tests, simplify
Nov 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions mypy/nodes.py
Expand Up @@ -136,9 +136,13 @@ def get_column(self) -> int:
'builtins.frozenset': 'typing.FrozenSet',
} # type: Final

nongen_builtins = {'builtins.tuple': 'typing.Tuple',
_nongen_builtins = {'builtins.tuple': 'typing.Tuple',
'builtins.enumerate': ''} # type: Final
AllanDaemon marked this conversation as resolved.
Show resolved Hide resolved
nongen_builtins.update((name, alias) for alias, name in type_aliases.items())
_nongen_builtins.update((name, alias) for alias, name in type_aliases.items())

AllanDaemon marked this conversation as resolved.
Show resolved Hide resolved
def get_nongen_builtins(python_version):
AllanDaemon marked this conversation as resolved.
Show resolved Hide resolved
# 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',
Expand Down
11 changes: 6 additions & 5 deletions mypy/semanal.py
Expand Up @@ -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_target_versions,
EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
ParamSpecExpr
Expand Down Expand Up @@ -446,7 +446,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)
Expand Down Expand Up @@ -2566,7 +2566,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)`
Expand Down Expand Up @@ -3787,12 +3787,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]]:
Expand Down
30 changes: 19 additions & 11 deletions mypy/typeanal.py
Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -342,7 +345,7 @@ 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:
Expand All @@ -364,7 +367,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:
Expand Down Expand Up @@ -970,9 +974,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).
Expand Down Expand Up @@ -1010,7 +1016,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.

Expand All @@ -1021,7 +1028,7 @@ 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.
Expand Down Expand Up @@ -1280,21 +1287,22 @@ 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)
1 change: 1 addition & 0 deletions test-data/unit/cmdline.test
Expand Up @@ -645,6 +645,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

Expand Down
11 changes: 6 additions & 5 deletions test-data/unit/pythoneval.test
Expand Up @@ -854,6 +854,7 @@ _program.py:19: error: Dict entry 0 has incompatible type "str": "List[<nothing>
_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
Expand All @@ -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
Expand Down