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 21, 2022
1 parent 4d989ce commit ae2adf9
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 17 deletions.
8 changes: 4 additions & 4 deletions .mypy/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -11872,7 +11872,7 @@
"code": "no-any-expr",
"column": 66,
"message": "Expression type contains \"Any\" (has type \"list[Any]\")",
"offset": 184,
"offset": 189,
"target": "mypy.main.process_options"
},
{
Expand Down Expand Up @@ -14282,7 +14282,7 @@
"code": "no-any-unimported",
"column": 8,
"message": "Type of variable becomes \"set[Any (from unimported type)]\" due to an unfollowed import",
"offset": 217,
"offset": 218,
"target": "mypy.options.Options.__init__"
},
{
Expand Down Expand Up @@ -16249,7 +16249,7 @@
"code": "truthy-bool",
"column": 15,
"message": "\"call\" has type \"CallExpr\" which does not implement __bool__ or __len__ so it could always be true in boolean context",
"offset": 938,
"offset": 939,
"target": "mypy.semanal.SemanticAnalyzer.extract_typevarlike_name"
},
{
Expand Down Expand Up @@ -16354,7 +16354,7 @@
"code": "no-any-expr",
"column": 37,
"message": "Expression type contains \"Any\" (has type \"type[CallableType]\")",
"offset": 562,
"offset": 577,
"target": "mypy.semanal.SemanticAnalyzer.create_getattr_var"
},
{
Expand Down
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 @@ -555,6 +555,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 @@ -130,6 +130,7 @@ def __init__(self) -> None:
self.targets: List[str] = []
self.ignore_any_from_error = True
self.incomplete_is_typed = flip_if_not_based(False)
self.implicit_typing_globals = True

# disallow_any options
self.disallow_any_generics = flip_if_not_based(True)
Expand Down
29 changes: 22 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,18 @@ 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"):
# 6a. typing
table = self.modules["typing"].names
if name in table:
node = table[name]
return node
# 6b. basedtyping
table = self.modules["basedtyping"].names
if name in table:
node = table[name]
return node
# Give up.
if not implicit_name and not suppress_errors:
self.name_not_defined(name, ctx)
Expand Down Expand Up @@ -4638,7 +4652,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 +4665,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
16 changes: 11 additions & 5 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 @@ -1141,7 +1141,7 @@ def bind_function_type_variables(
"""Find the type variables of the function type and bind them in our tvar_scope"""
if fun_type.variables:
for var in fun_type.variables:
var_node = self.lookup_qualified(var.name, defn)
var_node = self.lookup_qualified(var.name, defn, annotation=True)
assert var_node, "Binding for function type variable not found within function"
var_expr = var_node.node
assert isinstance(var_expr, TypeVarLikeExpr)
Expand Down Expand Up @@ -1440,11 +1440,17 @@ def flatten_tvars(ll: Iterable[List[T]]) -> List[T]:
return remove_dups(chain.from_iterable(ll))


class _LookupFn(Protocol):
def __call__(
self, __a: str, __b: Context, __c: bool = ..., annotation: bool = ...
) -> Optional[SymbolTableNode]: ...


class TypeVarLikeQuery(TypeQuery[TypeVarLikeList]):
"""Find TypeVar and ParamSpec references in an unbound type."""

def __init__(self,
lookup: Callable[[str, Context], Optional[SymbolTableNode]],
lookup: _LookupFn,
scope: 'TypeVarLikeScope',
*,
include_callables: bool = True,
Expand Down Expand Up @@ -1474,7 +1480,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 ae2adf9

Please sign in to comment.