Skip to content

Replace --disallow-any flags with separate boolean flags. #4178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 30, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 29 additions & 36 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,35 @@ directory. The four possible values are:
main.py:1: note: Import of 'submodule' ignored
main.py:1: note: (Using --follow-imports=error, module not passed on command line)

Disallow Any Flags
*****************************
The ``--disallow-any`` family of flags disallows various types of ``Any`` in a module.
The following options are available:

- ``--disallow-any-unimported`` disallows usage of types that come from unfollowed imports
(such types become aliases for ``Any``). Unfollowed imports occur either
when the imported module does not exist or when ``--follow-imports=skip``
is set.

- ``--disallow-any-expr`` disallows all expressions in the module that have type ``Any``.
If an expression of type ``Any`` appears anywhere in the module
mypy will output an error unless the expression is immediately
used as an argument to ``cast`` or assigned to a variable with an
explicit type annotation. In addition, declaring a variable of type ``Any``
or casting to type ``Any`` is not allowed. Note that calling functions
that take parameters of type ``Any`` is still allowed.

- ``--disallow-any-decorated`` disallows functions that have ``Any`` in their signature
after decorator transformation.

- ``--disallow-any-explicit`` disallows explicit ``Any`` in type positions such as type
annotations and generic type parameters.

- ``--disallow-any-generics`` disallows usage of generic types that do not specify explicit
type parameters. Moreover, built-in collections (such as ``list`` and
``dict``) become disallowed as you should use their aliases from the typing
module (such as ``List[int]`` and ``Dict[str, str]``).


Additional command line flags
*****************************
Expand Down Expand Up @@ -277,42 +306,6 @@ Here are some more useful flags:
re-check your code without ``--strict-optional`` to ensure new type errors
are not introduced.

.. _disallow-any:

- ``--disallow-any`` disallows various types of ``Any`` in a module.
The option takes a comma-separated list of the following values:
``unimported``, ``unannotated``, ``expr``, ``decorated``, ``explicit``,
``generics``.

``unimported`` disallows usage of types that come from unfollowed imports
(such types become aliases for ``Any``). Unfollowed imports occur either
when the imported module does not exist or when ``--follow-imports=skip``
is set.

``unannotated`` disallows function definitions that are not fully
typed (i.e. that are missing an explicit type annotation for any
of the parameters or the return type). ``unannotated`` option is
interchangeable with ``--disallow-untyped-defs``.

``expr`` disallows all expressions in the module that have type ``Any``.
If an expression of type ``Any`` appears anywhere in the module
mypy will output an error unless the expression is immediately
used as an argument to ``cast`` or assigned to a variable with an
explicit type annotation. In addition, declaring a variable of type ``Any``
or casting to type ``Any`` is not allowed. Note that calling functions
that take parameters of type ``Any`` is still allowed.

``decorated`` disallows functions that have ``Any`` in their signature
after decorator transformation.

``explicit`` disallows explicit ``Any`` in type positions such as type
annotations and generic type parameters.

``generics`` disallows usage of generic types that do not specify explicit
type parameters. Moreover, built-in collections (such as ``list`` and
``dict``) become disallowed as you should use their aliases from the typing
module (such as ``List[int]`` and ``Dict[str, str]``).

- ``--disallow-untyped-defs`` reports an error whenever it encounters
a function definition without type annotations.

Expand Down
19 changes: 14 additions & 5 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,20 @@ overridden by the pattern sections matching the module name.
- ``almost_silent`` (Boolean, deprecated) equivalent to
``follow_imports=skip``.

- ``disallow_any`` (Comma-separated list, default empty) is an option to
disallow various types of ``Any`` in a module. The flag takes a
comma-separated list of the following arguments: ``unimported``,
``unannotated``, ``expr``, ``decorated``, ``explicit``, ``generics``.
For explanations see the discussion for the :ref:`--disallow-any <disallow-any>` option.
- ``disallow_any_unimported`` (Boolean, default false) disallows usage of types that come
from unfollowed imports (such types become aliases for ``Any``).

- ``disallow_any_expr`` (Boolean, default false) disallows all expressions in the module
that have type ``Any``.

- ``disallow_any_decorated`` (Boolean, default false) disallows functions that have ``Any``
in their signature after decorator transformation.

- ``disallow_any_explicit`` (Boolean, default false) disallows explicit ``Any`` in type
positions such as type annotations and generic type parameters.

- ``disallow_any_generics`` (Boolean, default false) disallows usage of generic types that
do not specify explicit type parameters.

- ``disallow_subclassing_any`` (Boolean, default False) disallows
subclassing a value of type ``Any``. See
Expand Down
6 changes: 3 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
item)

self.check_for_missing_annotations(fdef)
if 'unimported' in self.options.disallow_any:
if self.options.disallow_any_unimported:
if fdef.type and isinstance(fdef.type, CallableType):
ret_type = fdef.type.ret_type
if has_any_from_unimported_type(ret_type):
Expand Down Expand Up @@ -1312,7 +1312,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
self.check_assignment(s.lvalues[-1], s.rvalue, s.type is None, s.new_syntax)

if (s.type is not None and
'unimported' in self.options.disallow_any and
self.options.disallow_any_unimported and
has_any_from_unimported_type(s.type)):
if isinstance(s.lvalues[-1], TupleExpr):
# This is a multiple assignment. Instead of figuring out which type is problematic,
Expand Down Expand Up @@ -2524,7 +2524,7 @@ def visit_with_stmt(self, s: WithStmt) -> None:
self.accept(s.body)

def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None:
if 'decorated' not in self.options.disallow_any or self.is_stub:
if not self.options.disallow_any_decorated or self.is_stub:
return

if mypy.checkexpr.has_any_type(typ):
Expand Down
6 changes: 3 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
options = self.chk.options
if options.warn_redundant_casts and is_same_type(source_type, target_type):
self.msg.redundant_cast(target_type, expr)
if 'unimported' in options.disallow_any and has_any_from_unimported_type(target_type):
if options.disallow_any_unimported and has_any_from_unimported_type(target_type):
self.msg.unimported_type_becomes_any("Target type of cast", target_type, expr)
check_for_explicit_any(target_type, self.chk.options, self.chk.is_typeshed_stub, self.msg,
context=expr)
Expand Down Expand Up @@ -2379,7 +2379,7 @@ def accept(self,
assert typ is not None
self.chk.store_type(node, typ)

if ('expr' in self.chk.options.disallow_any and
if (self.chk.options.disallow_any_expr and
not always_allow_any and
not self.chk.is_stub and
self.chk.in_checked_function() and
Expand Down Expand Up @@ -2558,7 +2558,7 @@ def visit_newtype_expr(self, e: NewTypeExpr) -> Type:
def visit_namedtuple_expr(self, e: NamedTupleExpr) -> Type:
tuple_type = e.info.tuple_type
if tuple_type:
if ('unimported' in self.chk.options.disallow_any and
if (self.chk.options.disallow_any_unimported and
has_any_from_unimported_type(tuple_type)):
self.msg.unimported_type_becomes_any("NamedTuple type", tuple_type, e)
check_for_explicit_any(tuple_type, self.chk.options, self.chk.is_typeshed_stub,
Expand Down
51 changes: 17 additions & 34 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,24 +119,6 @@ def type_check_only(sources: List[BuildSource], bin_dir: Optional[str],
options=options)


disallow_any_options = ['unimported', 'expr', 'unannotated', 'decorated', 'explicit', 'generics']


def disallow_any_argument_type(raw_options: str) -> List[str]:
if not raw_options:
# empty string disables all options
return []
flag_options = [o.strip() for o in raw_options.split(',')]
for option in flag_options:
if option not in disallow_any_options:
formatted_valid_options = ', '.join(
"'{}'".format(o) for o in disallow_any_options)
message = "Invalid '--disallow-any' option '{}' (valid options are: {}).".format(
option, formatted_valid_options)
raise argparse.ArgumentError(None, message)
return flag_options


FOOTER = """environment variables:
MYPYPATH additional module search path"""

Expand Down Expand Up @@ -272,10 +254,18 @@ def add_invertible_flag(flag: str,
help="silently ignore imports of missing modules")
parser.add_argument('--follow-imports', choices=['normal', 'silent', 'skip', 'error'],
default='normal', help="how to treat imports (default normal)")
parser.add_argument('--disallow-any', type=disallow_any_argument_type, default=[],
metavar='{{{}}}'.format(', '.join(disallow_any_options)),
help="disallow various types of Any in a module. Takes a comma-separated "
"list of options (defaults to all options disabled)")
parser.add_argument('--disallow-any-unimported', default=False, action='store_true',
help="disallow usage of types that come from unfollowed imports")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "disallow Any types resulting from unfollowed imports" ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this seems reasonable! Done!

parser.add_argument('--disallow-any-expr', default=False, action='store_true',
help='disallow all expressions in the module that have type Any')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd drop "in the module" since that's implicit (and you don't say it for the other flags).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep!

parser.add_argument('--disallow-any-decorated', default=False, action='store_true',
help='disallow functions that have Any in their signature '
'after decorator transformation')
parser.add_argument('--disallow-any-explicit', default=False, action='store_true',
help='disallow explicit Any in type positions')
parser.add_argument('--disallow-any-generics', default=False, action='store_true',
help='disallow usage of generic types that do not specify explicit '
'type parameters')
add_invertible_flag('--disallow-untyped-calls', default=False, strict_flag=True,
help="disallow calling functions without type annotations"
" from functions with type annotations")
Expand Down Expand Up @@ -364,6 +354,8 @@ def add_invertible_flag(flag: str,
# --dump-graph will dump the contents of the graph of SCCs and exit.
parser.add_argument('--dump-graph', action='store_true', help=argparse.SUPPRESS)
# deprecated options
parser.add_argument('--disallow-any', dest='special-opts:disallow_any',
help=argparse.SUPPRESS)
add_invertible_flag('--strict-boolean', default=False,
help=argparse.SUPPRESS)
parser.add_argument('-f', '--dirty-stubs', action='store_true',
Expand Down Expand Up @@ -438,6 +430,9 @@ def add_invertible_flag(flag: str,
)

# Process deprecated options
if special_opts.disallow_any:
print("--disallow-any option was split up into multiple flags. "
"See http://mypy.readthedocs.io/en/latest/command_line.html#disallow-any-flags")
if options.strict_boolean:
print("Warning: --strict-boolean is deprecated; "
"see https://github.com/python/mypy/issues/3195", file=sys.stderr)
Expand All @@ -462,9 +457,6 @@ def add_invertible_flag(flag: str,
print("Warning: --no-fast-parser no longer has any effect. The fast parser "
"is now mypy's default and only parser.")

if 'unannotated' in options.disallow_any:
options.disallow_untyped_defs = True

# Check for invalid argument combinations.
if require_targets:
code_methods = sum(bool(c) for c in [special_opts.modules,
Expand Down Expand Up @@ -652,7 +644,6 @@ def get_init_file(dir: str) -> Optional[str]:
'custom_typeshed_dir': str,
'mypy_path': lambda s: [p.strip() for p in re.split('[,:]', s)],
'junit_xml': str,
'disallow_any': disallow_any_argument_type,
# These two are for backwards compatibility
'silent_imports': bool,
'almost_silent': bool,
Expand Down Expand Up @@ -770,14 +761,6 @@ def parse_section(prefix: str, template: Options,
except ValueError as err:
print("%s: %s: %s" % (prefix, key, err), file=sys.stderr)
continue
if key == 'disallow_any':
# "disallow_any = " should disable all disallow_any options, including untyped defs,
# given in a more general config.
if not v:
results['disallow_untyped_defs'] = False
# If "unannotated" is explicitly given, turn on disallow_untyped_defs.
elif 'unannotated' in v:
results['disallow_untyped_defs'] = True
if key == 'silent_imports':
print("%s: silent_imports has been replaced by "
"ignore_missing_imports=True; follow_imports=skip" % prefix, file=sys.stderr)
Expand Down
14 changes: 12 additions & 2 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ class Options:
PER_MODULE_OPTIONS = {
"ignore_missing_imports",
"follow_imports",
"disallow_any",
"disallow_any_generics",
"disallow_any_unimported",
"disallow_any_expr",
"disallow_any_decorated",
"disallow_any_explicit",
"disallow_subclassing_any",
"disallow_untyped_calls",
"disallow_untyped_defs",
Expand Down Expand Up @@ -51,7 +55,13 @@ def __init__(self) -> None:
self.report_dirs = {} # type: Dict[str, str]
self.ignore_missing_imports = False
self.follow_imports = 'normal' # normal|silent|skip|error
self.disallow_any = [] # type: List[str]

# disallow_any options
self.disallow_any_generics = False
self.disallow_any_unimported = False
self.disallow_any_expr = False
self.disallow_any_decorated = False
self.disallow_any_explicit = False

# Disallow calling untyped functions from typed ones
self.disallow_untyped_calls = False
Expand Down
8 changes: 4 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,7 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
else:
self.fail('Invalid base class', base_expr)
info.fallback_to_any = True
if 'unimported' in self.options.disallow_any and has_any_from_unimported_type(base):
if self.options.disallow_any_unimported and has_any_from_unimported_type(base):
if isinstance(base_expr, (NameExpr, MemberExpr)):
prefix = "Base type {}".format(base_expr.name)
else:
Expand Down Expand Up @@ -2004,7 +2004,7 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None:
check_for_explicit_any(old_type, self.options, self.is_typeshed_stub_file, self.msg,
context=s)

if 'unimported' in self.options.disallow_any and has_any_from_unimported_type(old_type):
if self.options.disallow_any_unimported and has_any_from_unimported_type(old_type):
self.msg.unimported_type_becomes_any("Argument 2 to NewType(...)", old_type, s)

# If so, add it to the symbol table.
Expand Down Expand Up @@ -2118,7 +2118,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None:
return
variance, upper_bound = res

if 'unimported' in self.options.disallow_any:
if self.options.disallow_any_unimported:
for idx, constraint in enumerate(values, start=1):
if has_any_from_unimported_type(constraint):
prefix = "Constraint {}".format(idx)
Expand Down Expand Up @@ -2587,7 +2587,7 @@ def parse_typeddict_args(self, call: CallExpr) -> Tuple[List[str], List[Type], b
check_for_explicit_any(t, self.options, self.is_typeshed_stub_file, self.msg,
context=call)

if 'unimported' in self.options.disallow_any:
if self.options.disallow_any_unimported:
for t in types:
if has_any_from_unimported_type(t):
self.msg.unimported_type_becomes_any("Type of a TypedDict key", t, dictexpr)
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal_pass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ def make_type_analyzer(self, indicator: Dict[str, bool]) -> TypeAnalyserPass3:
indicator)

def check_for_omitted_generics(self, typ: Type) -> None:
if 'generics' not in self.options.disallow_any or self.is_typeshed_file:
if not self.options.disallow_any_generics or self.is_typeshed_file:
return

for t in collect_any_types(typ):
Expand Down
6 changes: 3 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
elif fullname == 'typing.Tuple':
if len(t.args) == 0 and not t.empty_tuple_index:
# Bare 'Tuple' is same as 'tuple'
if 'generics' in self.options.disallow_any and not self.is_typeshed_stub:
if self.options.disallow_any_generics and not self.is_typeshed_stub:
self.fail(messages.BARE_GENERIC, t)
typ = self.named_type('builtins.tuple', line=t.line, column=t.column)
typ.from_generic_builtin = True
Expand Down Expand Up @@ -669,7 +669,7 @@ def visit_instance(self, t: Instance) -> None:
if len(t.args) != len(info.type_vars):
if len(t.args) == 0:
from_builtins = t.type.fullname() in nongen_builtins and not t.from_generic_builtin
if ('generics' in self.options.disallow_any and
if (self.options.disallow_any_generics and
not self.is_typeshed_stub and
from_builtins):
alternative = nongen_builtins[t.type.fullname()]
Expand Down Expand Up @@ -930,7 +930,7 @@ def check_for_explicit_any(typ: Optional[Type],
is_typeshed_stub: bool,
msg: MessageBuilder,
context: Context) -> None:
if ('explicit' in options.disallow_any and
if (options.disallow_any_explicit and
not is_typeshed_stub and
typ and
has_explicit_any(typ)):
Expand Down
2 changes: 1 addition & 1 deletion mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def TypeOfAny(x: str) -> str:
unannotated = TypeOfAny('unannotated')
# Does this Any come from an explicit type annotation?
explicit = TypeOfAny('explicit')
# Does this come from an unfollowed import? See --disallow-any=unimported option
# Does this come from an unfollowed import? See --disallow-any-unimported option
from_unimported_type = TypeOfAny('from_unimported_type')
# Does this Any type come from omitted generics?
from_omitted_generics = TypeOfAny('from_omitted_generics')
Expand Down
3 changes: 2 additions & 1 deletion mypy_self_check.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ disallow_subclassing_any = True
warn_no_return = True
strict_optional = True
no_implicit_optional = True
disallow_any = generics, unimported
disallow_any_generics = True
disallow_any_unimported = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_unused_configs = True
Expand Down
Loading