Skip to content

Commit ae2adf9

Browse files
committed
Allow typing members in annotations without imports
1 parent 4d989ce commit ae2adf9

File tree

7 files changed

+49
-17
lines changed

7 files changed

+49
-17
lines changed

.mypy/baseline.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -11872,7 +11872,7 @@
1187211872
"code": "no-any-expr",
1187311873
"column": 66,
1187411874
"message": "Expression type contains \"Any\" (has type \"list[Any]\")",
11875-
"offset": 184,
11875+
"offset": 189,
1187611876
"target": "mypy.main.process_options"
1187711877
},
1187811878
{
@@ -14282,7 +14282,7 @@
1428214282
"code": "no-any-unimported",
1428314283
"column": 8,
1428414284
"message": "Type of variable becomes \"set[Any (from unimported type)]\" due to an unfollowed import",
14285-
"offset": 217,
14285+
"offset": 218,
1428614286
"target": "mypy.options.Options.__init__"
1428714287
},
1428814288
{
@@ -16249,7 +16249,7 @@
1624916249
"code": "truthy-bool",
1625016250
"column": 15,
1625116251
"message": "\"call\" has type \"CallExpr\" which does not implement __bool__ or __len__ so it could always be true in boolean context",
16252-
"offset": 938,
16252+
"offset": 939,
1625316253
"target": "mypy.semanal.SemanticAnalyzer.extract_typevarlike_name"
1625416254
},
1625516255
{
@@ -16354,7 +16354,7 @@
1635416354
"code": "no-any-expr",
1635516355
"column": 37,
1635616356
"message": "Expression type contains \"Any\" (has type \"type[CallableType]\")",
16357-
"offset": 562,
16357+
"offset": 577,
1635816358
"target": "mypy.semanal.SemanticAnalyzer.create_getattr_var"
1635916359
},
1636016360
{

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
## [1.4.0]
66
### Added
7+
- When `__future__.annotations`, all `typing`s are usable without imports
8+
9+
## [Unreleased]
10+
### Added
711
- `ignore_any_from_errors` option to suppress `no-any-expr` messages from other errors
812
- Function types are inferred from Overloads, overrides and default values. (no overrides for now sorry)
913
- Infer Property types

mypy/main.py

+5
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,11 @@ def add_invertible_flag(flag: str,
555555
'--incomplete-is-typed', group=based_group, default=False,
556556
help="Partially typed/incomplete functions in this module are considered typed"
557557
" for untyped call errors.")
558+
based_group.add_argument(
559+
'--disable-implicit-typing-globals', action="store_false",
560+
dest="implicit_typing_globals",
561+
help="Disallow using members from `typing` in annotations without imports."
562+
)
558563

559564
config_group = parser.add_argument_group(
560565
title='Config file',

mypy/options.py

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def __init__(self) -> None:
130130
self.targets: List[str] = []
131131
self.ignore_any_from_error = True
132132
self.incomplete_is_typed = flip_if_not_based(False)
133+
self.implicit_typing_globals = True
133134

134135
# disallow_any options
135136
self.disallow_any_generics = flip_if_not_based(True)

mypy/semanal.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -2752,7 +2752,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
27522752

27532753
pep_613 = False
27542754
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
2755-
lookup = self.lookup_qualified(s.unanalyzed_type.name, s, suppress_errors=True)
2755+
lookup = self.lookup_qualified(s.unanalyzed_type.name, s, suppress_errors=True,
2756+
annotation=True)
27562757
if lookup and lookup.fullname in TYPE_ALIAS_NAMES:
27572758
pep_613 = True
27582759
if not pep_613 and s.unanalyzed_type is not None:
@@ -3533,15 +3534,15 @@ def check_classvar(self, s: AssignmentStmt) -> None:
35333534
def is_classvar(self, typ: Type) -> bool:
35343535
if not isinstance(typ, UnboundType):
35353536
return False
3536-
sym = self.lookup_qualified(typ.name, typ)
3537+
sym = self.lookup_qualified(typ.name, typ, annotation=True)
35373538
if not sym or not sym.node:
35383539
return False
35393540
return sym.node.fullname == 'typing.ClassVar'
35403541

35413542
def is_final_type(self, typ: Optional[Type]) -> bool:
35423543
if not isinstance(typ, UnboundType):
35433544
return False
3544-
sym = self.lookup_qualified(typ.name, typ)
3545+
sym = self.lookup_qualified(typ.name, typ, annotation=True)
35453546
if not sym or not sym.node:
35463547
return False
35473548
return sym.node.fullname in FINAL_TYPE_NAMES
@@ -4512,7 +4513,8 @@ def visit_class_pattern(self, p: ClassPattern) -> None:
45124513
#
45134514

45144515
def lookup(self, name: str, ctx: Context,
4515-
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
4516+
suppress_errors: bool = False,
4517+
annotation: bool = False) -> Optional[SymbolTableNode]:
45164518
"""Look up an unqualified (no dots) name in all active namespaces.
45174519
45184520
Note that the result may contain a PlaceholderNode. The caller may
@@ -4568,6 +4570,18 @@ def lookup(self, name: str, ctx: Context,
45684570
return None
45694571
node = table[name]
45704572
return node
4573+
# 6. based
4574+
if annotation and self.is_future_flag_set("annotations"):
4575+
# 6a. typing
4576+
table = self.modules["typing"].names
4577+
if name in table:
4578+
node = table[name]
4579+
return node
4580+
# 6b. basedtyping
4581+
table = self.modules["basedtyping"].names
4582+
if name in table:
4583+
node = table[name]
4584+
return node
45714585
# Give up.
45724586
if not implicit_name and not suppress_errors:
45734587
self.name_not_defined(name, ctx)
@@ -4638,7 +4652,8 @@ def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
46384652
return module_prefix(self.modules, fullname) == self.cur_mod_id
46394653

46404654
def lookup_qualified(self, name: str, ctx: Context,
4641-
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
4655+
suppress_errors: bool = False,
4656+
annotation: bool = False) -> Optional[SymbolTableNode]:
46424657
"""Lookup a qualified name in all activate namespaces.
46434658
46444659
Note that the result may contain a PlaceholderNode. The caller may
@@ -4650,10 +4665,10 @@ def lookup_qualified(self, name: str, ctx: Context,
46504665
"""
46514666
if '.' not in name:
46524667
# Simple case: look up a short name.
4653-
return self.lookup(name, ctx, suppress_errors=suppress_errors)
4668+
return self.lookup(name, ctx, suppress_errors=suppress_errors, annotation=annotation)
46544669
parts = name.split('.')
46554670
namespace = self.cur_mod_id
4656-
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors)
4671+
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors, annotation=annotation)
46574672
if sym:
46584673
for i in range(1, len(parts)):
46594674
node = sym.node

mypy/semanal_shared.py

+2-1
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

+11-5
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) ->
178178
return typ
179179

180180
def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type:
181-
sym = self.lookup_qualified(t.name, t)
181+
sym = self.lookup_qualified(t.name, t, annotation=True)
182182
if sym is not None:
183183
node = sym.node
184184
if isinstance(node, PlaceholderNode):
@@ -696,7 +696,7 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:
696696

697697
def anal_type_guard(self, t: Type) -> Optional[Type]:
698698
if isinstance(t, UnboundType):
699-
sym = self.lookup_qualified(t.name, t)
699+
sym = self.lookup_qualified(t.name, t, annotation=True)
700700
if sym is not None and sym.node is not None:
701701
return self.anal_type_guard_arg(t, sym.node.fullname)
702702
# TODO: What if it's an Instance? Then use t.type.fullname?
@@ -1141,7 +1141,7 @@ def bind_function_type_variables(
11411141
"""Find the type variables of the function type and bind them in our tvar_scope"""
11421142
if fun_type.variables:
11431143
for var in fun_type.variables:
1144-
var_node = self.lookup_qualified(var.name, defn)
1144+
var_node = self.lookup_qualified(var.name, defn, annotation=True)
11451145
assert var_node, "Binding for function type variable not found within function"
11461146
var_expr = var_node.node
11471147
assert isinstance(var_expr, TypeVarLikeExpr)
@@ -1440,11 +1440,17 @@ def flatten_tvars(ll: Iterable[List[T]]) -> List[T]:
14401440
return remove_dups(chain.from_iterable(ll))
14411441

14421442

1443+
class _LookupFn(Protocol):
1444+
def __call__(
1445+
self, __a: str, __b: Context, __c: bool = ..., annotation: bool = ...
1446+
) -> Optional[SymbolTableNode]: ...
1447+
1448+
14431449
class TypeVarLikeQuery(TypeQuery[TypeVarLikeList]):
14441450
"""Find TypeVar and ParamSpec references in an unbound type."""
14451451

14461452
def __init__(self,
1447-
lookup: Callable[[str, Context], Optional[SymbolTableNode]],
1453+
lookup: _LookupFn,
14481454
scope: 'TypeVarLikeScope',
14491455
*,
14501456
include_callables: bool = True,
@@ -1474,7 +1480,7 @@ def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList:
14741480
node = n
14751481
name = base
14761482
if node is None:
1477-
node = self.lookup(name, t)
1483+
node = self.lookup(name, t, annotation=True)
14781484
if node and isinstance(node.node, TypeVarLikeExpr) and (
14791485
self.include_bound_tvars or self.scope.get_binding(node) is None):
14801486
assert isinstance(node.node, TypeVarLikeExpr)

0 commit comments

Comments
 (0)