Skip to content

Commit

Permalink
Allow typing members in annotations without imports
Browse files Browse the repository at this point in the history
  • Loading branch information
KotlinIsland committed Jul 20, 2022
1 parent 2b519d1 commit 21ce924
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## [1.4.0]
### Added
- When `__future__.annotations`, all `typing`s are usable without imports

## [Unreleased]
### Added
- `ignore_any_from_errors` option to suppress `no-any-expr` messages from other errors
- Function types are inferred from Overloads, overrides and default values. (no overrides for now sorry)
- Infer Property types
Expand Down
5 changes: 5 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,11 @@ def add_invertible_flag(flag: str,
'--incomplete-is-typed', group=based_group, default=False,
help="Partially typed/incomplete functions in this module are considered typed"
" for untyped call errors.")
based_group.add_argument(
'--disable-implicit-typing-globals', action="store_false",
dest="implicit_typing_globals",
help="Disallow using members from `typing` in annotations without imports."
)

config_group = parser.add_argument_group(
title='Config file',
Expand Down
1 change: 1 addition & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def __init__(self) -> None:
self.ignore_any_from_error = True
self.incomplete_is_typed = flip_if_not_based(False)
self.baseline_as_notes = False
self.implicit_typing_globals = True

# disallow_any options
self.disallow_any_generics = flip_if_not_based(True)
Expand Down
33 changes: 26 additions & 7 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2752,7 +2752,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:

pep_613 = False
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
lookup = self.lookup_qualified(s.unanalyzed_type.name, s, suppress_errors=True)
lookup = self.lookup_qualified(s.unanalyzed_type.name, s, suppress_errors=True,
annotation=True)
if lookup and lookup.fullname in TYPE_ALIAS_NAMES:
pep_613 = True
if not pep_613 and s.unanalyzed_type is not None:
Expand Down Expand Up @@ -3533,15 +3534,15 @@ def check_classvar(self, s: AssignmentStmt) -> None:
def is_classvar(self, typ: Type) -> bool:
if not isinstance(typ, UnboundType):
return False
sym = self.lookup_qualified(typ.name, typ)
sym = self.lookup_qualified(typ.name, typ, annotation=True)
if not sym or not sym.node:
return False
return sym.node.fullname == 'typing.ClassVar'

def is_final_type(self, typ: Optional[Type]) -> bool:
if not isinstance(typ, UnboundType):
return False
sym = self.lookup_qualified(typ.name, typ)
sym = self.lookup_qualified(typ.name, typ, annotation=True)
if not sym or not sym.node:
return False
return sym.node.fullname in FINAL_TYPE_NAMES
Expand Down Expand Up @@ -4512,7 +4513,8 @@ def visit_class_pattern(self, p: ClassPattern) -> None:
#

def lookup(self, name: str, ctx: Context,
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
suppress_errors: bool = False,
annotation: bool = False) -> Optional[SymbolTableNode]:
"""Look up an unqualified (no dots) name in all active namespaces.
Note that the result may contain a PlaceholderNode. The caller may
Expand Down Expand Up @@ -4568,6 +4570,22 @@ def lookup(self, name: str, ctx: Context,
return None
node = table[name]
return node
# 6. based
if annotation and (
self.is_future_flag_set("annotations")
or self.options.python_version >= (3, 11)
):
# 6a. typing
table = self.modules["typing"].names
if name in table:
node = table[name]
return node
# 6b. implicit T
if name == "T":
return SymbolTableNode(
GDEF,
TypeVarExpr("T", "T", [], self.object_type())
)
# Give up.
if not implicit_name and not suppress_errors:
self.name_not_defined(name, ctx)
Expand Down Expand Up @@ -4638,7 +4656,8 @@ def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
return module_prefix(self.modules, fullname) == self.cur_mod_id

def lookup_qualified(self, name: str, ctx: Context,
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
suppress_errors: bool = False,
annotation: bool = False) -> Optional[SymbolTableNode]:
"""Lookup a qualified name in all activate namespaces.
Note that the result may contain a PlaceholderNode. The caller may
Expand All @@ -4650,10 +4669,10 @@ def lookup_qualified(self, name: str, ctx: Context,
"""
if '.' not in name:
# Simple case: look up a short name.
return self.lookup(name, ctx, suppress_errors=suppress_errors)
return self.lookup(name, ctx, suppress_errors=suppress_errors, annotation=annotation)
parts = name.split('.')
namespace = self.cur_mod_id
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors)
sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors, annotation=annotation)
if sym:
for i in range(1, len(parts)):
node = sym.node
Expand Down
3 changes: 2 additions & 1 deletion mypy/semanal_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class SemanticAnalyzerCoreInterface:

@abstractmethod
def lookup_qualified(self, name: str, ctx: Context,
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
suppress_errors: bool = False
, annotation: bool = False) -> Optional[SymbolTableNode]:
raise NotImplementedError

@abstractmethod
Expand Down
6 changes: 3 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def visit_unbound_type(self, t: UnboundType, defining_literal: bool = False) ->
return typ

def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) -> Type:
sym = self.lookup_qualified(t.name, t)
sym = self.lookup_qualified(t.name, t, annotation=True)
if sym is not None:
node = sym.node
if isinstance(node, PlaceholderNode):
Expand Down Expand Up @@ -696,7 +696,7 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:

def anal_type_guard(self, t: Type) -> Optional[Type]:
if isinstance(t, UnboundType):
sym = self.lookup_qualified(t.name, t)
sym = self.lookup_qualified(t.name, t, annotation=True)
if sym is not None and sym.node is not None:
return self.anal_type_guard_arg(t, sym.node.fullname)
# TODO: What if it's an Instance? Then use t.type.fullname?
Expand Down Expand Up @@ -1474,7 +1474,7 @@ def visit_unbound_type(self, t: UnboundType) -> TypeVarLikeList:
node = n
name = base
if node is None:
node = self.lookup(name, t)
node = self.lookup(name, t, annotation=True)
if node and isinstance(node.node, TypeVarLikeExpr) and (
self.include_bound_tvars or self.scope.get_binding(node) is None):
assert isinstance(node.node, TypeVarLikeExpr)
Expand Down

0 comments on commit 21ce924

Please sign in to comment.