From d9ea9920d68918be18666a0b8d2888bf8cb6c127 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Dec 2021 16:46:36 +0300 Subject: [PATCH 1/5] Allows to use `type[T]` in stubs --- mypy/typeanal.py | 23 +++++++++++++---------- test-data/unit/check-generic-alias.test | 3 +++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e29ccfd3c928..5c9259651f47 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -163,6 +163,14 @@ def __init__(self, # Names of type aliases encountered while analysing a type will be collected here. self.aliases_used: Set[str] = set() + @property + def is_future_annotations(self) -> bool: + return ( + self.options.python_version >= (3, 9) + or self.api.is_future_flag_set('annotations') + or self.allow_new_syntax # basically tells us if this is a stub + ) + def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) -> Type: typ = self.visit_unbound_type_nonoptional(t, defining_literal) if t.optional: @@ -203,8 +211,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return hook(AnalyzeTypeContext(t, t, self)) if (fullname in get_nongen_builtins(self.options.python_version) and t.args and - not self.allow_new_syntax and - not self.api.is_future_flag_set("annotations")): + not self.is_future_annotations): self.fail(no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t) tvar_def = self.tvar_scope.get_binding(sym) @@ -291,9 +298,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " in a variable annotation", t) return AnyType(TypeOfAny.from_error) elif (fullname == 'typing.Tuple' or - (fullname == 'builtins.tuple' and (self.options.python_version >= (3, 9) or - self.api.is_future_flag_set('annotations') or - self.allow_new_syntax))): + (fullname == 'builtins.tuple' and self.is_future_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') @@ -326,8 +331,7 @@ 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.options.python_version >= (3, 9) or - self.api.is_future_flag_set('annotations')))): + (fullname == 'builtins.type' and self.is_future_annotations)): if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) @@ -405,6 +409,7 @@ def analyze_type_with_type_info( ctx.line, ctx.column) # Check type argument count. if len(instance.args) != len(info.type_vars) and not self.defining_alias: + # raise ValueError(instance) fix_instance(instance, self.fail, self.note, disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub, @@ -704,9 +709,7 @@ def visit_star_type(self, t: StarType) -> Type: def visit_union_type(self, t: UnionType) -> Type: if (t.uses_pep604_syntax is True and t.is_evaluated is True - and self.api.is_stub_file is False - and self.options.python_version < (3, 10) - and self.api.is_future_flag_set('annotations') is False): + and not self.is_future_annotations): self.fail("X | Y syntax for unions requires Python 3.10", t) return UnionType(self.anal_array(t.items), t.line) diff --git a/test-data/unit/check-generic-alias.test b/test-data/unit/check-generic-alias.test index b6060f3922fe..bfef0f5c5086 100644 --- a/test-data/unit/check-generic-alias.test +++ b/test-data/unit/check-generic-alias.test @@ -273,6 +273,8 @@ b: B import m reveal_type(m.a) # N: Revealed type is "builtins.list[builtins.int]" reveal_type(m.b) # N: Revealed type is "builtins.list[builtins.list[builtins.int]]" +m.C # has complex representation, ignored +reveal_type(m.d) # N: Revealed type is "Type[builtins.str]" [file m.pyi] A = list[int] @@ -281,4 +283,5 @@ B = list[list[int]] b: B class C(list[int]): pass +d: type[str] [builtins fixtures/list.pyi] From f79f3e4f0ebc7bc4c5e0e5b9839f421d2981e1b4 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Dec 2021 16:48:43 +0300 Subject: [PATCH 2/5] Removes unused comment --- mypy/typeanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 5c9259651f47..a707141e3e36 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -409,7 +409,6 @@ def analyze_type_with_type_info( ctx.line, ctx.column) # Check type argument count. if len(instance.args) != len(info.type_vars) and not self.defining_alias: - # raise ValueError(instance) fix_instance(instance, self.fail, self.note, disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub, From f35e9ceeea78f1960f2bc9c8278a287f497bebde Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Dec 2021 17:21:39 +0300 Subject: [PATCH 3/5] Fixes CI --- mypy/typeanal.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index a707141e3e36..6800e0ded148 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -163,10 +163,10 @@ def __init__(self, # Names of type aliases encountered while analysing a type will be collected here. self.aliases_used: Set[str] = set() - @property - def is_future_annotations(self) -> bool: + def is_future_annotations(self, target_version: Optional[Tuple[int, int]] = None) -> bool: return ( - self.options.python_version >= (3, 9) + (self.options.python_version >= target_version + if target_version is not None else False) or self.api.is_future_flag_set('annotations') or self.allow_new_syntax # basically tells us if this is a stub ) @@ -211,7 +211,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) return hook(AnalyzeTypeContext(t, t, self)) if (fullname in get_nongen_builtins(self.options.python_version) and t.args and - not self.is_future_annotations): + not self.is_future_annotations()): self.fail(no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t) tvar_def = self.tvar_scope.get_binding(sym) @@ -298,7 +298,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " in a variable annotation", t) return AnyType(TypeOfAny.from_error) elif (fullname == 'typing.Tuple' or - (fullname == 'builtins.tuple' and self.is_future_annotations)): + (fullname == 'builtins.tuple' and self.is_future_annotations((3, 9)))): # 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') @@ -331,7 +331,7 @@ 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.is_future_annotations)): + (fullname == 'builtins.type' and self.is_future_annotations((3, 9)))): if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) @@ -708,7 +708,7 @@ def visit_star_type(self, t: StarType) -> Type: def visit_union_type(self, t: UnionType) -> Type: if (t.uses_pep604_syntax is True and t.is_evaluated is True - and not self.is_future_annotations): + and not self.is_future_annotations((3, 10))): self.fail("X | Y syntax for unions requires Python 3.10", t) return UnionType(self.anal_array(t.items), t.line) From a4a040dc2660442c400ca574745549d67e9e18f7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 30 Dec 2021 12:49:05 +0300 Subject: [PATCH 4/5] Addresses review --- mypy/semanal.py | 4 +--- mypy/typeanal.py | 36 ++++++++++++++++-------------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index a9226d2cdd0c..2b3956612eab 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2629,7 +2629,6 @@ def analyze_alias(self, rvalue: Expression, self.plugin, self.options, self.is_typeshed_stub_file, - allow_new_syntax=self.is_stub_file, allow_placeholder=allow_placeholder, in_dynamic_func=dynamic, global_scope=global_scope) @@ -5183,8 +5182,7 @@ def type_analyzer(self, *, allow_tuple_literal=allow_tuple_literal, report_invalid_types=report_invalid_types, allow_placeholder=allow_placeholder, - allow_required=allow_required, - allow_new_syntax=self.is_stub_file) + allow_required=allow_required) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack return tpan diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 6800e0ded148..0f7b87853dc3 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -70,7 +70,6 @@ def analyze_type_alias(node: Expression, plugin: Plugin, options: Options, is_typeshed_stub: bool, - allow_new_syntax: bool = False, allow_placeholder: bool = False, in_dynamic_func: bool = False, global_scope: bool = True) -> Optional[Tuple[Type, Set[str]]]: @@ -80,13 +79,13 @@ def analyze_type_alias(node: Expression, full names of type aliases it depends on (directly or indirectly). Return None otherwise. 'node' must have been semantically analyzed. """ - try: - type = expr_to_unanalyzed_type(node, options, allow_new_syntax) + try: # TODO: refactor `expr_to_type` to work with `__future__` import + type = expr_to_unanalyzed_type(node, options, api.is_stub_file) except TypeTranslationError: api.fail('Invalid type alias: expression is not a valid type', node) return None analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, - allow_new_syntax=allow_new_syntax, defining_alias=True, + defining_alias=True, allow_placeholder=allow_placeholder) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope @@ -127,7 +126,6 @@ def __init__(self, is_typeshed_stub: bool, *, defining_alias: bool = False, allow_tuple_literal: bool = False, - allow_new_syntax: bool = False, allow_unbound_tvars: bool = False, allow_placeholder: bool = False, allow_required: bool = False, @@ -144,8 +142,11 @@ def __init__(self, # Positive if we are analyzing arguments of another (outer) type self.nesting_level = 0 # Should we allow new type syntax when targeting older Python versions - # like 'list[int]' or 'X | Y' (allowed in stubs)? - self.allow_new_syntax = allow_new_syntax + # like 'list[int]' or 'X | Y' (allowed in stubs and with `__future__` import)? + self.always_allow_new_syntax = ( + self.api.is_stub_file + or self.api.is_future_flag_set('annotations') + ) # Should we accept unbound type variables (always OK in aliases)? self.allow_unbound_tvars = allow_unbound_tvars or defining_alias # If false, record incomplete ref if we generate PlaceholderType. @@ -163,14 +164,6 @@ def __init__(self, # Names of type aliases encountered while analysing a type will be collected here. self.aliases_used: Set[str] = set() - def is_future_annotations(self, target_version: Optional[Tuple[int, int]] = None) -> bool: - return ( - (self.options.python_version >= target_version - if target_version is not None else False) - or self.api.is_future_flag_set('annotations') - or self.allow_new_syntax # basically tells us if this is a stub - ) - def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) -> Type: typ = self.visit_unbound_type_nonoptional(t, defining_literal) if t.optional: @@ -210,8 +203,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) if hook is not None: return hook(AnalyzeTypeContext(t, t, self)) if (fullname in get_nongen_builtins(self.options.python_version) - and t.args and - not self.is_future_annotations()): + and t.args + and not self.always_allow_new_syntax): self.fail(no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t) tvar_def = self.tvar_scope.get_binding(sym) @@ -298,7 +291,8 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " in a variable annotation", t) return AnyType(TypeOfAny.from_error) elif (fullname == 'typing.Tuple' or - (fullname == 'builtins.tuple' and self.is_future_annotations((3, 9)))): + (fullname == 'builtins.tuple' + and (self.always_allow_new_syntax or self.options.python_version >= (3, 9)))): # 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') @@ -331,7 +325,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.is_future_annotations((3, 9)))): + (fullname == 'builtins.type' + and (self.always_allow_new_syntax or self.options.python_version >= (3, 9)))): if len(t.args) == 0: if fullname == 'typing.Type': any_type = self.get_omitted_any(t) @@ -708,7 +703,8 @@ def visit_star_type(self, t: StarType) -> Type: def visit_union_type(self, t: UnionType) -> Type: if (t.uses_pep604_syntax is True and t.is_evaluated is True - and not self.is_future_annotations((3, 10))): + and not self.always_allow_new_syntax + and not self.options.python_version >= (3, 10)): self.fail("X | Y syntax for unions requires Python 3.10", t) return UnionType(self.anal_array(t.items), t.line) From 3c0fe262ac71262a4c1dddcb781b6802e0642e5a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 30 Dec 2021 12:56:47 +0300 Subject: [PATCH 5/5] Remove TODO --- mypy/typeanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0f7b87853dc3..969c756cb8b5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -79,7 +79,7 @@ def analyze_type_alias(node: Expression, full names of type aliases it depends on (directly or indirectly). Return None otherwise. 'node' must have been semantically analyzed. """ - try: # TODO: refactor `expr_to_type` to work with `__future__` import + try: type = expr_to_unanalyzed_type(node, options, api.is_stub_file) except TypeTranslationError: api.fail('Invalid type alias: expression is not a valid type', node)