Skip to content

Commit bc77c9c

Browse files
committed
Allow typing members in annotations without imports
1 parent 3394c7b commit bc77c9c

File tree

6 files changed

+43
-13
lines changed

6 files changed

+43
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44
### Added
55
- Unions in output messages show with new syntax
6+
- When `__future__.annotations`, all `typing`s are usable without imports
67

78
## [1.0.0]
89
### Added

mypy/main.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,13 +500,21 @@ def add_invertible_flag(flag: str,
500500
help="Show program's version number and exit",
501501
stdout=stdout)
502502

503-
general_group.add_argument(
503+
based_group = parser.add_argument_group(
504+
title='Based functionality arguments')
505+
based_group.add_argument(
504506
'--write-baseline', action="store_true",
505507
help="Create an error baseline from the result of this execution")
506-
general_group.add_argument(
508+
based_group.add_argument(
507509
'--baseline-file', action='store',
508510
help="Use baseline info in the given file"
509511
"(defaults to '{}')".format(defaults.BASELINE_FILE))
512+
based_group.add_argument(
513+
'--disable-implicit-typing-globals', action="store_false",
514+
dest="implicit_typing_globals",
515+
help="Disallow using members from `typing` in annotations without imports."
516+
)
517+
510518
config_group = parser.add_argument_group(
511519
title='Config file',
512520
description="Use a config file instead of command line arguments. "

mypy/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ def __init__(self) -> None:
103103
# File names, directory names or subpaths to avoid checking
104104
self.exclude: List[str] = []
105105

106+
# Based options
106107
self.write_baseline = False
107108
self.baseline_file = defaults.BASELINE_FILE
109+
self.implicit_typing_globals = True
108110

109111
# disallow_any options
110112
self.disallow_any_generics = True

mypy/semanal.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,7 +2636,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
26362636

26372637
pep_613 = False
26382638
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
2639-
lookup = self.lookup(s.unanalyzed_type.name, s, suppress_errors=True)
2639+
lookup = self.lookup(s.unanalyzed_type.name, s, suppress_errors=True, annotation=True)
26402640
if lookup and lookup.fullname in ("typing.TypeAlias", "typing_extensions.TypeAlias"):
26412641
pep_613 = True
26422642
if s.unanalyzed_type is not None and not pep_613:
@@ -3351,15 +3351,15 @@ def check_classvar(self, s: AssignmentStmt) -> None:
33513351
def is_classvar(self, typ: Type) -> bool:
33523352
if not isinstance(typ, UnboundType):
33533353
return False
3354-
sym = self.lookup_qualified(typ.name, typ)
3354+
sym = self.lookup_qualified(typ.name, typ, annotation=True)
33553355
if not sym or not sym.node:
33563356
return False
33573357
return sym.node.fullname == 'typing.ClassVar'
33583358

33593359
def is_final_type(self, typ: Optional[Type]) -> bool:
33603360
if not isinstance(typ, UnboundType):
33613361
return False
3362-
sym = self.lookup_qualified(typ.name, typ)
3362+
sym = self.lookup_qualified(typ.name, typ, annotation=True)
33633363
if not sym or not sym.node:
33643364
return False
33653365
return sym.node.fullname in ('typing.Final', 'typing_extensions.Final')
@@ -4205,7 +4205,8 @@ def visit_await_expr(self, expr: AwaitExpr) -> None:
42054205
#
42064206

42074207
def lookup(self, name: str, ctx: Context,
4208-
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
4208+
suppress_errors: bool = False,
4209+
annotation: bool = False) -> Optional[SymbolTableNode]:
42094210
"""Look up an unqualified (no dots) name in all active namespaces.
42104211
42114212
Note that the result may contain a PlaceholderNode. The caller may
@@ -4261,6 +4262,22 @@ def lookup(self, name: str, ctx: Context,
42614262
return None
42624263
node = table[name]
42634264
return node
4265+
# 6. based
4266+
if annotation and (
4267+
self.is_future_flag_set("annotations")
4268+
or self.options.python_version >= (3, 11)
4269+
):
4270+
# 6a. typing
4271+
table = self.modules["typing"].names
4272+
if name in table:
4273+
node = table[name]
4274+
return node
4275+
# 6b. implicit T
4276+
if name == "T":
4277+
return SymbolTableNode(
4278+
GDEF,
4279+
TypeVarExpr("T", "T", [], self.object_type())
4280+
)
42644281
# Give up.
42654282
if not implicit_name and not suppress_errors:
42664283
self.name_not_defined(name, ctx)
@@ -4329,7 +4346,8 @@ def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
43294346
return module_prefix(self.modules, fullname) == self.cur_mod_id
43304347

43314348
def lookup_qualified(self, name: str, ctx: Context,
4332-
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
4349+
suppress_errors: bool = False,
4350+
annotation: bool = False) -> Optional[SymbolTableNode]:
43334351
"""Lookup a qualified name in all activate namespaces.
43344352
43354353
Note that the result may contain a PlaceholderNode. The caller may
@@ -4341,10 +4359,10 @@ def lookup_qualified(self, name: str, ctx: Context,
43414359
"""
43424360
if '.' not in name:
43434361
# Simple case: look up a short name.
4344-
return self.lookup(name, ctx, suppress_errors=suppress_errors)
4362+
return self.lookup(name, ctx, suppress_errors=suppress_errors, annotation=annotation)
43454363
parts = name.split('.')
43464364
namespace = self.cur_mod_id
4347-
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors)
4365+
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors, annotation=annotation)
43484366
if sym:
43494367
for i in range(1, len(parts)):
43504368
node = sym.node

mypy/semanal_shared.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class SemanticAnalyzerCoreInterface:
3434

3535
@abstractmethod
3636
def lookup_qualified(self, name: str, ctx: Context,
37-
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
37+
suppress_errors: bool = False
38+
, annotation: bool = False) -> Optional[SymbolTableNode]:
3839
raise NotImplementedError
3940

4041
@abstractmethod

mypy/typeanal.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) ->
169169
return typ
170170

171171
def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type:
172-
sym = self.lookup_qualified(t.name, t)
172+
sym = self.lookup_qualified(t.name, t, annotation=True)
173173
if sym is not None:
174174
node = sym.node
175175
if isinstance(node, PlaceholderNode):
@@ -565,7 +565,7 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:
565565

566566
def anal_type_guard(self, t: Type) -> Optional[Type]:
567567
if isinstance(t, UnboundType):
568-
sym = self.lookup_qualified(t.name, t)
568+
sym = self.lookup_qualified(t.name, t, annotation=True)
569569
if sym is not None and sym.node is not None:
570570
return self.anal_type_guard_arg(t, sym.node.fullname)
571571
# TODO: What if it's an Instance? Then use t.type.fullname?
@@ -1260,7 +1260,7 @@ def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList:
12601260
node = n
12611261
name = base
12621262
if node is None:
1263-
node = self.lookup(name, t)
1263+
node = self.lookup(name, t, annotation=True)
12641264
if node and isinstance(node.node, TypeVarLikeExpr) and (
12651265
self.include_bound_tvars or self.scope.get_binding(node) is None):
12661266
assert isinstance(node.node, TypeVarLikeExpr)

0 commit comments

Comments
 (0)