From b8d56ba7cb6262593e94e4a83131ecd7d7672154 Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 12:12:24 +1100 Subject: [PATCH 1/6] Add --strict Changes mypy behaviour re: strict checking flags from opt-in to opt-out. Also adds --no-* options for all affected flags. Fixes #2585 --- docs/source/command_line.rst | 10 +++++++++- mypy/main.py | 27 ++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index e5d1a8018bfb6..afae515d733c4 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -16,7 +16,7 @@ flag (or its long form ``--help``):: [--warn-incomplete-stub] [--warn-redundant-casts] [--warn-no-return] [--warn-unused-ignores] [--show-error-context] [--fast-parser] [-i] [--cache-dir DIR] [--strict-optional] - [--strict-optional-whitelist [GLOB [GLOB ...]]] + [--strict-optional-whitelist [GLOB [GLOB ...]]] [--strict] [--junit-xml JUNIT_XML] [--pdb] [--show-traceback] [--stats] [--inferstats] [--custom-typing MODULE] [--custom-typeshed-dir DIR] [--scripts-are-modules] @@ -366,6 +366,14 @@ Here are some more useful flags: also currently ignores functions with an empty body or a body that is just ellipsis (``...``), since these can be valid as abstract methods. +- ``--strict`` mode enables the strictest mypy configuration. It changes + mypy's behaviour regarding new strict flags from opt-in to opt-out. + You can see the list of flags enabled by strict mode in the full ``mypy -h`` + output. + + For all flags enabled by strict, you can opt-out individually using + ``--no-``. + For the remaining flags you can read the full ``mypy -h`` output. .. note:: diff --git a/mypy/main.py b/mypy/main.py index a2c3649a8844d..d4ce00d0d1f82 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -157,11 +157,17 @@ def process_options(args: List[str], parser.add_argument('--disallow-untyped-calls', action='store_true', help="disallow calling functions without type annotations" " from functions with type annotations") + parser.add_argument('--no-disallow-untyped-calls', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--disallow-untyped-defs', action='store_true', help="disallow defining functions without type annotations" " or with incomplete type annotations") + parser.add_argument('--no-disallow-untyped-defs', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--check-untyped-defs', action='store_true', help="type check the interior of functions without type annotations") + parser.add_argument('--no-check-untyped-defs', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--disallow-subclassing-any', action='store_true', help="disallow subclassing values of type 'Any' when defining classes") parser.add_argument('--warn-incomplete-stub', action='store_true', @@ -169,10 +175,14 @@ def process_options(args: List[str], " --check-untyped-defs enabled") parser.add_argument('--warn-redundant-casts', action='store_true', help="warn about casting an expression to its inferred type") + parser.add_argument('--no-warn-redundant-casts', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--warn-no-return', action='store_true', help="warn about functions that end without returning") parser.add_argument('--warn-unused-ignores', action='store_true', help="warn about unneeded '# type: ignore' comments") + parser.add_argument('--no-warn-unused-ignores', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--show-error-context', action='store_false', dest='hide_error_context', help='Precede errors with "note:" messages explaining context') @@ -186,11 +196,16 @@ def process_options(args: List[str], parser.add_argument('--strict-optional', action='store_true', dest='strict_optional', help="enable experimental strict Optional checks") + parser.add_argument('--no-strict-optional', action='store_false', + help=argparse.SUPPRESS) parser.add_argument('--strict-optional-whitelist', metavar='GLOB', nargs='*', help="suppress strict Optional errors in all but the provided files " "(experimental -- read documentation before using!). " "Implies --strict-optional. Has the undesirable side-effect of " "suppressing other errors in non-whitelisted files.") + parser.add_argument('--strict', action='store_true', + dest='special-opts:strict', + help="Strict mode. Enable --strict-optional, --warn-unused-ignores, --warn-redundant-casts, --check-untyped-defs, --disallow-untyped-defs, and --disallow-untyped-calls") parser.add_argument('--junit-xml', help="write junit.xml to the given file") parser.add_argument('--pdb', action='store_true', help="invoke pdb on fatal error") parser.add_argument('--show-traceback', '--tb', action='store_true', @@ -213,6 +228,7 @@ def process_options(args: List[str], parser.add_argument('--find-occurrences', metavar='CLASS.MEMBER', dest='special-opts:find_occurrences', help="print out all usages of a class member (experimental)") + # hidden options # --shadow-file a.py tmp.py will typecheck tmp.py in place of a.py. # Useful for tools to make transformations to a file to get more @@ -274,7 +290,7 @@ def process_options(args: List[str], if dummy.config_file: config_file = dummy.config_file if not os.path.exists(config_file): - parser.error("Cannot file config file '%s'" % config_file) + parser.error("Cannot find config file '%s'" % config_file) # Parse config file first, so command line can override. options = Options() @@ -321,6 +337,15 @@ def process_options(args: List[str], elif code_methods > 1: parser.error("May only specify one of: module, package, files, or command.") + # Strict mode opts-in the user to all strict flags + if special_opts.strict: + options.strict_optional = options.no_strict_optional + options.warn_unused_ignores = options.no_warn_unused_ignores + options.warn_redundant_casts = options.no_warn_redundant_casts + options.check_untyped_defs = options.no_check_untyped_defs + options.disallow_untyped_defs = options.no_disallow_untyped_defs + options.disallow_untyped_calls = options.no_disallow_untyped_calls + # Set build flags. if options.strict_optional_whitelist is not None: # TODO: Deprecate, then kill this flag From 3c8883b0b3fa077837ebdff65fbcb7ddc5ad0283 Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 12:29:06 +1100 Subject: [PATCH 2/6] Move --no-* options to special-opts --- mypy/main.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index d4ce00d0d1f82..98a10deb206ac 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -158,15 +158,18 @@ def process_options(args: List[str], help="disallow calling functions without type annotations" " from functions with type annotations") parser.add_argument('--no-disallow-untyped-calls', action='store_false', + dest='special-opts:no_disallow_untyped_calls', help=argparse.SUPPRESS) parser.add_argument('--disallow-untyped-defs', action='store_true', help="disallow defining functions without type annotations" " or with incomplete type annotations") parser.add_argument('--no-disallow-untyped-defs', action='store_false', + dest='special-opts:no_disallow_untyped_defs', help=argparse.SUPPRESS) parser.add_argument('--check-untyped-defs', action='store_true', help="type check the interior of functions without type annotations") parser.add_argument('--no-check-untyped-defs', action='store_false', + dest='special-opts:no_check_untyped_defs', help=argparse.SUPPRESS) parser.add_argument('--disallow-subclassing-any', action='store_true', help="disallow subclassing values of type 'Any' when defining classes") @@ -176,12 +179,14 @@ def process_options(args: List[str], parser.add_argument('--warn-redundant-casts', action='store_true', help="warn about casting an expression to its inferred type") parser.add_argument('--no-warn-redundant-casts', action='store_false', + dest='special-opts:no_warn_redundant_casts', help=argparse.SUPPRESS) parser.add_argument('--warn-no-return', action='store_true', help="warn about functions that end without returning") parser.add_argument('--warn-unused-ignores', action='store_true', help="warn about unneeded '# type: ignore' comments") parser.add_argument('--no-warn-unused-ignores', action='store_false', + dest='special-opts:no_warn_unused_ignores', help=argparse.SUPPRESS) parser.add_argument('--show-error-context', action='store_false', dest='hide_error_context', @@ -197,6 +202,7 @@ def process_options(args: List[str], dest='strict_optional', help="enable experimental strict Optional checks") parser.add_argument('--no-strict-optional', action='store_false', + dest='special-opts:no_strict_optional', help=argparse.SUPPRESS) parser.add_argument('--strict-optional-whitelist', metavar='GLOB', nargs='*', help="suppress strict Optional errors in all but the provided files " @@ -339,12 +345,12 @@ def process_options(args: List[str], # Strict mode opts-in the user to all strict flags if special_opts.strict: - options.strict_optional = options.no_strict_optional - options.warn_unused_ignores = options.no_warn_unused_ignores - options.warn_redundant_casts = options.no_warn_redundant_casts - options.check_untyped_defs = options.no_check_untyped_defs - options.disallow_untyped_defs = options.no_disallow_untyped_defs - options.disallow_untyped_calls = options.no_disallow_untyped_calls + options.strict_optional = special_opts.no_strict_optional + options.warn_unused_ignores = special_opts.no_warn_unused_ignores + options.warn_redundant_casts = special_opts.no_warn_redundant_casts + options.check_untyped_defs = special_opts.no_check_untyped_defs + options.disallow_untyped_defs = special_opts.no_disallow_untyped_defs + options.disallow_untyped_calls = special_opts.no_disallow_untyped_calls # Set build flags. if options.strict_optional_whitelist is not None: From 16ffd490a139c60e81c5ba972ba0a4b530e86cde Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 12:36:59 +1100 Subject: [PATCH 3/6] Fix line length --- mypy/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/main.py b/mypy/main.py index 98a10deb206ac..8222a1aa57332 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -211,7 +211,9 @@ def process_options(args: List[str], "suppressing other errors in non-whitelisted files.") parser.add_argument('--strict', action='store_true', dest='special-opts:strict', - help="Strict mode. Enable --strict-optional, --warn-unused-ignores, --warn-redundant-casts, --check-untyped-defs, --disallow-untyped-defs, and --disallow-untyped-calls") + help=("Strict mode. Enable --strict-optional, --warn-unused-ignores, " + "--warn-redundant-casts, --check-untyped-defs, " + "--disallow-untyped-defs, and --disallow-untyped-calls")) parser.add_argument('--junit-xml', help="write junit.xml to the given file") parser.add_argument('--pdb', action='store_true', help="invoke pdb on fatal error") parser.add_argument('--show-traceback', '--tb', action='store_true', From 184ee1e22954f79378f976690b53b18e034418dd Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 13:39:28 +1100 Subject: [PATCH 4/6] Make --no-* arguments work beyond --strict --- mypy/main.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 8222a1aa57332..490e2a510697f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -347,12 +347,26 @@ def process_options(args: List[str], # Strict mode opts-in the user to all strict flags if special_opts.strict: - options.strict_optional = special_opts.no_strict_optional - options.warn_unused_ignores = special_opts.no_warn_unused_ignores - options.warn_redundant_casts = special_opts.no_warn_redundant_casts - options.check_untyped_defs = special_opts.no_check_untyped_defs - options.disallow_untyped_defs = special_opts.no_disallow_untyped_defs - options.disallow_untyped_calls = special_opts.no_disallow_untyped_calls + options.strict_optional = True + options.warn_unused_ignores = True + options.warn_redundant_casts = True + options.check_untyped_defs = True + options.disallow_untyped_defs = True + options.disallow_untyped_calls = True + + # Handle --no-* args + if special_opts.no_strict_optional: + options.strict_optional = False + if special_opts.no_warn_unused_ignores: + options.warn_unused_ignores = False + if special_opts.no_warn_redundant_casts: + options.warn_redundant_casts = False + if special_opts.no_check_untyped_defs: + options.check_untyped_defs = False + if special_opts.no_disallow_untyped_defs: + options.disallow_untyped_defs = False + if special_opts.no_disallow_untyped_calls: + options.disallow_untyped_calls = False # Set build flags. if options.strict_optional_whitelist is not None: From 4f4a679f2ffd470829a8835166db9a878fe27613 Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 13:44:47 +1100 Subject: [PATCH 5/6] Fix action for --no- args --- mypy/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 490e2a510697f..a7456969e9aa6 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -157,18 +157,18 @@ def process_options(args: List[str], parser.add_argument('--disallow-untyped-calls', action='store_true', help="disallow calling functions without type annotations" " from functions with type annotations") - parser.add_argument('--no-disallow-untyped-calls', action='store_false', + parser.add_argument('--no-disallow-untyped-calls', action='store_true', dest='special-opts:no_disallow_untyped_calls', help=argparse.SUPPRESS) parser.add_argument('--disallow-untyped-defs', action='store_true', help="disallow defining functions without type annotations" " or with incomplete type annotations") - parser.add_argument('--no-disallow-untyped-defs', action='store_false', + parser.add_argument('--no-disallow-untyped-defs', action='store_true', dest='special-opts:no_disallow_untyped_defs', help=argparse.SUPPRESS) parser.add_argument('--check-untyped-defs', action='store_true', help="type check the interior of functions without type annotations") - parser.add_argument('--no-check-untyped-defs', action='store_false', + parser.add_argument('--no-check-untyped-defs', action='store_true', dest='special-opts:no_check_untyped_defs', help=argparse.SUPPRESS) parser.add_argument('--disallow-subclassing-any', action='store_true', @@ -178,14 +178,14 @@ def process_options(args: List[str], " --check-untyped-defs enabled") parser.add_argument('--warn-redundant-casts', action='store_true', help="warn about casting an expression to its inferred type") - parser.add_argument('--no-warn-redundant-casts', action='store_false', + parser.add_argument('--no-warn-redundant-casts', action='store_true', dest='special-opts:no_warn_redundant_casts', help=argparse.SUPPRESS) parser.add_argument('--warn-no-return', action='store_true', help="warn about functions that end without returning") parser.add_argument('--warn-unused-ignores', action='store_true', help="warn about unneeded '# type: ignore' comments") - parser.add_argument('--no-warn-unused-ignores', action='store_false', + parser.add_argument('--no-warn-unused-ignores', action='store_true', dest='special-opts:no_warn_unused_ignores', help=argparse.SUPPRESS) parser.add_argument('--show-error-context', action='store_false', @@ -201,7 +201,7 @@ def process_options(args: List[str], parser.add_argument('--strict-optional', action='store_true', dest='strict_optional', help="enable experimental strict Optional checks") - parser.add_argument('--no-strict-optional', action='store_false', + parser.add_argument('--no-strict-optional', action='store_true', dest='special-opts:no_strict_optional', help=argparse.SUPPRESS) parser.add_argument('--strict-optional-whitelist', metavar='GLOB', nargs='*', From 87ba30df0331687fc21552d545c30973039311dc Mon Sep 17 00:00:00 2001 From: Alex Jurkiewicz Date: Tue, 17 Jan 2017 23:25:46 +1100 Subject: [PATCH 6/6] Simplify strict option handling Still requires boilerplate for affected options: * Two options (one hidden) * Override default dest value * Special type for mypy.options.Options slot * Add to mypy.options.Options.STRICT_OPTIONS But most of these can be factored away with more work --- mypy/main.py | 61 ++++++++++++++++++++++--------------------------- mypy/options.py | 27 +++++++++++++++++----- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index a7456969e9aa6..8fbb774206405 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -126,6 +126,12 @@ def __init__(self, prog: Optional[str]) -> None: super().__init__(prog=prog, max_help_position=28) +# Take a ternary option affected by --strict and set it correctly +def _set_strict_option(options: Options, opt: str, strict: bool) -> None: + if getattr(options, opt) is None: + setattr(options, opt, strict) + + def process_options(args: List[str], require_targets: bool = True ) -> Tuple[List[BuildSource], Options]: @@ -157,20 +163,23 @@ def process_options(args: List[str], parser.add_argument('--disallow-untyped-calls', action='store_true', help="disallow calling functions without type annotations" " from functions with type annotations") - parser.add_argument('--no-disallow-untyped-calls', action='store_true', - dest='special-opts:no_disallow_untyped_calls', + parser.add_argument('--no-disallow-untyped-calls', action='store_false', + dest='disallow_untyped_calls', help=argparse.SUPPRESS) + parser.set_defaults(disallow_untyped_calls=None) parser.add_argument('--disallow-untyped-defs', action='store_true', help="disallow defining functions without type annotations" " or with incomplete type annotations") - parser.add_argument('--no-disallow-untyped-defs', action='store_true', - dest='special-opts:no_disallow_untyped_defs', + parser.add_argument('--no-disallow-untyped-defs', action='store_false', + dest='disallow_untyped_defs', help=argparse.SUPPRESS) + parser.set_defaults(disallow_untyped_defs=None) parser.add_argument('--check-untyped-defs', action='store_true', help="type check the interior of functions without type annotations") - parser.add_argument('--no-check-untyped-defs', action='store_true', - dest='special-opts:no_check_untyped_defs', + parser.add_argument('--no-check-untyped-defs', action='store_false', + dest='check_untyped_defs', help=argparse.SUPPRESS) + parser.set_defaults(check_untyped_defs=None) parser.add_argument('--disallow-subclassing-any', action='store_true', help="disallow subclassing values of type 'Any' when defining classes") parser.add_argument('--warn-incomplete-stub', action='store_true', @@ -178,16 +187,18 @@ def process_options(args: List[str], " --check-untyped-defs enabled") parser.add_argument('--warn-redundant-casts', action='store_true', help="warn about casting an expression to its inferred type") - parser.add_argument('--no-warn-redundant-casts', action='store_true', - dest='special-opts:no_warn_redundant_casts', + parser.add_argument('--no-warn-redundant-casts', action='store_false', + dest='warn_redundant_casts', help=argparse.SUPPRESS) + parser.set_defaults(warn_redundant_casts=None) parser.add_argument('--warn-no-return', action='store_true', help="warn about functions that end without returning") parser.add_argument('--warn-unused-ignores', action='store_true', help="warn about unneeded '# type: ignore' comments") - parser.add_argument('--no-warn-unused-ignores', action='store_true', - dest='special-opts:no_warn_unused_ignores', + parser.add_argument('--no-warn-unused-ignores', action='store_false', + dest='warn_unused_ignores', help=argparse.SUPPRESS) + parser.set_defaults(warn_unused_ignores=None) parser.add_argument('--show-error-context', action='store_false', dest='hide_error_context', help='Precede errors with "note:" messages explaining context') @@ -201,9 +212,10 @@ def process_options(args: List[str], parser.add_argument('--strict-optional', action='store_true', dest='strict_optional', help="enable experimental strict Optional checks") - parser.add_argument('--no-strict-optional', action='store_true', - dest='special-opts:no_strict_optional', + parser.add_argument('--no-strict-optional', action='store_false', + dest='strict_optional', help=argparse.SUPPRESS) + parser.set_defaults(strict_optional=None) parser.add_argument('--strict-optional-whitelist', metavar='GLOB', nargs='*', help="suppress strict Optional errors in all but the provided files " "(experimental -- read documentation before using!). " @@ -345,28 +357,9 @@ def process_options(args: List[str], elif code_methods > 1: parser.error("May only specify one of: module, package, files, or command.") - # Strict mode opts-in the user to all strict flags - if special_opts.strict: - options.strict_optional = True - options.warn_unused_ignores = True - options.warn_redundant_casts = True - options.check_untyped_defs = True - options.disallow_untyped_defs = True - options.disallow_untyped_calls = True - - # Handle --no-* args - if special_opts.no_strict_optional: - options.strict_optional = False - if special_opts.no_warn_unused_ignores: - options.warn_unused_ignores = False - if special_opts.no_warn_redundant_casts: - options.warn_redundant_casts = False - if special_opts.no_check_untyped_defs: - options.check_untyped_defs = False - if special_opts.no_disallow_untyped_defs: - options.disallow_untyped_defs = False - if special_opts.no_disallow_untyped_calls: - options.disallow_untyped_calls = False + # Determine if strict options should be enabled or disabled + for strict_opt in options.STRICT_OPTIONS: + _set_strict_option(options, strict_opt, special_opts.strict) # Set build flags. if options.strict_optional_whitelist is not None: diff --git a/mypy/options.py b/mypy/options.py index a9b6a05bb1384..bacca4a101a23 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -29,6 +29,15 @@ class Options: "ignore_errors", } + STRICT_OPTIONS = { + "strict_optional", + "warn_unused_ignores", + "warn_redundant_casts", + "check_untyped_defs", + "disallow_untyped_defs", + "disallow_untyped_calls", + } + OPTIONS_AFFECTING_CACHE = PER_MODULE_OPTIONS | {"strict_optional"} def __init__(self) -> None: @@ -44,13 +53,16 @@ def __init__(self) -> None: self.follow_imports = 'normal' # normal|silent|skip|error # Disallow calling untyped functions from typed ones - self.disallow_untyped_calls = False + # Enabled in strict mode only + self.disallow_untyped_calls = None # type: Optional[bool] # Disallow defining untyped (or incompletely typed) functions - self.disallow_untyped_defs = False + # Enabled in strict mode only + self.disallow_untyped_defs = None # type: Optional[bool] # Type check unannotated functions - self.check_untyped_defs = False + # Enabled in strict mode only + self.check_untyped_defs = None # type: Optional[bool] # Disallow subclassing values of type 'Any' self.disallow_subclassing_any = False @@ -59,19 +71,22 @@ def __init__(self) -> None: self.warn_incomplete_stub = False # Warn about casting an expression to its inferred type - self.warn_redundant_casts = False + # Enabled in strict mode only + self.warn_redundant_casts = None # type: Optional[bool] # Warn about falling off the end of a function returning non-None self.warn_no_return = False # Warn about unused '# type: ignore' comments - self.warn_unused_ignores = False + # Enabled in strict mode only + self.warn_unused_ignores = None # type: Optional[bool] # Files in which to ignore all non-fatal errors self.ignore_errors = False # Apply strict None checking - self.strict_optional = False + # Enabled in strict mode only + self.strict_optional = None # type: Optional[bool] # Hide "note: In function "foo":" messages. self.hide_error_context = True