Ejemplo n.º 1
0
    def test_find_sources_in_dir_namespace_explicit_base(self) -> None:
        options = Options()
        options.namespace_packages = True
        options.explicit_package_bases = True
        options.mypy_path = ["/"]

        files = {
            "/pkg/a1/b/c/d/e.py",
            "/pkg/a1/b/f.py",
            "/pkg/a2/__init__.py",
            "/pkg/a2/b/c/d/e.py",
            "/pkg/a2/b/f.py",
        }
        finder = SourceFinder(FakeFSCache(files), options)
        assert find_sources_in_dir(finder, "/") == [
            ("pkg.a1.b.c.d.e", "/"),
            ("pkg.a1.b.f", "/"),
            ("pkg.a2", "/"),
            ("pkg.a2.b.c.d.e", "/"),
            ("pkg.a2.b.f", "/"),
        ]

        options.mypy_path = ["/pkg"]
        finder = SourceFinder(FakeFSCache(files), options)
        assert find_sources_in_dir(finder, "/") == [
            ("a1.b.c.d.e", "/pkg"),
            ("a1.b.f", "/pkg"),
            ("a2", "/pkg"),
            ("a2.b.c.d.e", "/pkg"),
            ("a2.b.f", "/pkg"),
        ]
Ejemplo n.º 2
0
    def __init__(self,
                 fgmanager: FineGrainedBuildManager,
                 *,
                 json: bool,
                 no_errors: bool = False,
                 no_any: bool = False,
                 try_text: bool = False,
                 flex_any: Optional[float] = None,
                 use_fixme: Optional[str] = None,
                 max_guesses: Optional[int] = None) -> None:
        self.fgmanager = fgmanager
        self.manager = fgmanager.manager
        self.plugin = self.manager.plugin
        self.graph = fgmanager.graph
        self.finder = SourceFinder(self.manager.fscache, self.manager.options)

        self.give_json = json
        self.no_errors = no_errors
        self.try_text = try_text
        self.flex_any = flex_any
        if no_any:
            self.flex_any = 1.0

        self.max_guesses = max_guesses or 64
        self.use_fixme = use_fixme
Ejemplo n.º 3
0
    def test_crawl_namespace_explicit_base(self) -> None:
        options = Options()
        options.namespace_packages = True
        options.explicit_package_bases = True

        finder = SourceFinder(FakeFSCache({"/setup.py"}), options)
        assert crawl(finder, "/setup.py") == ("setup", "/")

        finder = SourceFinder(FakeFSCache({"/a/setup.py"}), options)
        assert crawl(finder, "/a/setup.py") == ("setup", "/a")

        finder = SourceFinder(FakeFSCache({"/a/b/setup.py"}), options)
        assert crawl(finder, "/a/b/setup.py") == ("setup", "/a/b")

        finder = SourceFinder(FakeFSCache({"/a/setup.py", "/a/__init__.py"}),
                              options)
        assert crawl(finder, "/a/setup.py") == ("a.setup", "/")

        finder = SourceFinder(
            FakeFSCache({"/a/invalid-name/setup.py", "/a/__init__.py"}),
            options,
        )
        assert crawl(finder, "/a/invalid-name/setup.py") == ("setup",
                                                             "/a/invalid-name")

        finder = SourceFinder(FakeFSCache({"/a/b/setup.py", "/a/__init__.py"}),
                              options)
        assert crawl(finder, "/a/b/setup.py") == ("a.b.setup", "/")

        finder = SourceFinder(
            FakeFSCache(
                {"/a/b/c/setup.py", "/a/__init__.py", "/a/b/c/__init__.py"}),
            options,
        )
        assert crawl(finder, "/a/b/c/setup.py") == ("a.b.c.setup", "/")

        # set mypy path, so we actually have some explicit base dirs
        options.mypy_path = ["/a/b"]

        finder = SourceFinder(FakeFSCache({"/a/b/c/setup.py"}), options)
        assert crawl(finder, "/a/b/c/setup.py") == ("c.setup", "/a/b")

        finder = SourceFinder(
            FakeFSCache(
                {"/a/b/c/setup.py", "/a/__init__.py", "/a/b/c/__init__.py"}),
            options,
        )
        assert crawl(finder, "/a/b/c/setup.py") == ("c.setup", "/a/b")

        options.mypy_path = ["/a/b", "/a/b/c"]
        finder = SourceFinder(FakeFSCache({"/a/b/c/setup.py"}), options)
        assert crawl(finder, "/a/b/c/setup.py") == ("setup", "/a/b/c")
Ejemplo n.º 4
0
    def test_find_sources_in_dir_namespace_multi_dir(self) -> None:
        options = Options()
        options.namespace_packages = True
        options.explicit_package_bases = True
        options.mypy_path = ["/a", "/b"]

        finder = SourceFinder(FakeFSCache({"/a/pkg/a.py", "/b/pkg/b.py"}), options)
        assert find_sources_in_dir(finder, "/") == [("pkg.a", "/a"), ("pkg.b", "/b")]
Ejemplo n.º 5
0
    def test_crawl_no_namespace(self) -> None:
        options = Options()
        options.namespace_packages = False

        finder = SourceFinder(FakeFSCache({"/setup.py"}), options)
        assert crawl(finder, "/setup.py") == ("setup", "/")

        finder = SourceFinder(FakeFSCache({"/a/setup.py"}), options)
        assert crawl(finder, "/a/setup.py") == ("setup", "/a")

        finder = SourceFinder(FakeFSCache({"/a/b/setup.py"}), options)
        assert crawl(finder, "/a/b/setup.py") == ("setup", "/a/b")

        finder = SourceFinder(FakeFSCache({"/a/setup.py", "/a/__init__.py"}),
                              options)
        assert crawl(finder, "/a/setup.py") == ("a.setup", "/")

        finder = SourceFinder(
            FakeFSCache({"/a/invalid-name/setup.py", "/a/__init__.py"}),
            options,
        )
        assert crawl(finder, "/a/invalid-name/setup.py") == ("setup",
                                                             "/a/invalid-name")

        finder = SourceFinder(FakeFSCache({"/a/b/setup.py", "/a/__init__.py"}),
                              options)
        assert crawl(finder, "/a/b/setup.py") == ("setup", "/a/b")

        finder = SourceFinder(
            FakeFSCache(
                {"/a/b/c/setup.py", "/a/__init__.py", "/a/b/c/__init__.py"}),
            options,
        )
        assert crawl(finder, "/a/b/c/setup.py") == ("c.setup", "/a/b")
Ejemplo n.º 6
0
    def test_find_sources_in_dir_namespace(self) -> None:
        options = Options()
        options.namespace_packages = True

        files = {
            "/pkg/a1/b/c/d/e.py",
            "/pkg/a1/b/f.py",
            "/pkg/a2/__init__.py",
            "/pkg/a2/b/c/d/e.py",
            "/pkg/a2/b/f.py",
        }
        finder = SourceFinder(FakeFSCache(files), options)
        assert find_sources_in_dir(finder, "/") == [
            ("a2", "/pkg"),
            ("a2.b.c.d.e", "/pkg"),
            ("a2.b.f", "/pkg"),
            ("e", "/pkg/a1/b/c/d"),
            ("f", "/pkg/a1/b"),
        ]
Ejemplo n.º 7
0
class SuggestionEngine:
    """Engine for finding call sites and suggesting signatures."""
    def __init__(self,
                 fgmanager: FineGrainedBuildManager,
                 *,
                 json: bool,
                 no_errors: bool = False,
                 no_any: bool = False,
                 try_text: bool = False,
                 flex_any: Optional[float] = None,
                 use_fixme: Optional[str] = None) -> None:
        self.fgmanager = fgmanager
        self.manager = fgmanager.manager
        self.plugin = self.manager.plugin
        self.graph = fgmanager.graph
        self.finder = SourceFinder(self.manager.fscache)

        self.give_json = json
        self.no_errors = no_errors
        self.try_text = try_text
        self.flex_any = flex_any
        if no_any:
            self.flex_any = 1.0

        self.max_guesses = 16
        self.use_fixme = use_fixme

    def suggest(self, function: str) -> str:
        """Suggest an inferred type for function."""
        mod, func_name, node = self.find_node(function)

        with self.restore_after(mod):
            with self.with_export_types():
                suggestion = self.get_suggestion(mod, node)

        if self.give_json:
            return self.json_suggestion(mod, func_name, node, suggestion)
        else:
            return self.format_signature(suggestion)

    def suggest_callsites(self, function: str) -> str:
        """Find a list of call sites of function."""
        mod, _, node = self.find_node(function)
        with self.restore_after(mod):
            callsites, _ = self.get_callsites(node)

        return '\n'.join(
            dedup([
                "%s:%s: %s" %
                (path, line, self.format_args(arg_kinds, arg_names, arg_types))
                for path, line, arg_kinds, _, arg_names, arg_types in callsites
            ]))

    @contextmanager
    def restore_after(self, module: str) -> Iterator[None]:
        """Context manager that reloads a module after executing the body.

        This should undo any damage done to the module state while mucking around.
        """
        try:
            yield
        finally:
            self.reload(self.graph[module])

    @contextmanager
    def with_export_types(self) -> Iterator[None]:
        """Context manager that enables the export_types flag in the body.

        This causes type information to be exported into the manager's all_types variable.
        """
        old = self.manager.options.export_types
        self.manager.options.export_types = True
        try:
            yield
        finally:
            self.manager.options.export_types = old

    def get_trivial_type(self, fdef: FuncDef) -> CallableType:
        """Generate a trivial callable type from a func def, with all Anys"""
        return CallableType(
            [AnyType(TypeOfAny.unannotated) for a in fdef.arg_kinds],
            fdef.arg_kinds,
            fdef.arg_names,
            # We call this a special form so that has_any_type doesn't consider it to be a real any
            AnyType(TypeOfAny.special_form),
            self.builtin_type('builtins.function'))

    def get_args(self, is_method: bool, base: CallableType,
                 defaults: List[Optional[Type]],
                 callsites: List[Callsite]) -> List[List[Type]]:
        """Produce a list of type suggestions for each argument type."""
        types = []  # type: List[List[Type]]
        for i in range(len(base.arg_kinds)):
            # Make self args Any but this will get overriden somewhere in the checker
            # We call this a special form so that has_any_type doesn't consider it to be a real any
            if i == 0 and is_method:
                types.append([AnyType(TypeOfAny.special_form)])
                continue

            all_arg_types = []
            for call in callsites:
                for typ in call.arg_types[i - is_method]:
                    # Collect all the types except for implicit anys
                    if not is_implicit_any(typ):
                        all_arg_types.append(typ)
            # Add in any default argument types
            default = defaults[i]
            if default:
                all_arg_types.append(default)

            if len(all_arg_types) == 1 and isinstance(
                    get_proper_type(all_arg_types[0]), NoneType):
                types.append([
                    UnionType.make_union(
                        [all_arg_types[0],
                         AnyType(TypeOfAny.explicit)])
                ])
            elif all_arg_types:
                types.append(generate_type_combinations(all_arg_types))
            else:
                # If we don't have anything, we'll try Any and object
                # (Actually object usually is bad for downstream consumers...)
                # types.append([AnyType(TypeOfAny.explicit), self.builtin_type('builtins.object')])
                types.append([AnyType(TypeOfAny.explicit)])
        return types

    def get_default_arg_types(self, state: State,
                              fdef: FuncDef) -> List[Optional[Type]]:
        return [
            self.manager.all_types[arg.initializer]
            if arg.initializer else None for arg in fdef.arguments
        ]

    def add_adjustments(self, typs: List[Type]) -> List[Type]:
        if not self.try_text or self.manager.options.python_version[0] != 2:
            return typs
        translator = StrToText(self.builtin_type)
        return dedup(typs + [tp.accept(translator) for tp in typs])

    def get_guesses(self, is_method: bool, base: CallableType,
                    defaults: List[Optional[Type]],
                    callsites: List[Callsite]) -> List[CallableType]:
        """Compute a list of guesses for a function's type.

        This focuses just on the argument types, and doesn't change the provided return type.
        """
        options = self.get_args(is_method, base, defaults, callsites)
        options = [self.add_adjustments(tps) for tps in options]
        return [
            base.copy_modified(arg_types=list(x))
            for x in itertools.product(*options)
        ]

    def get_callsites(self, func: FuncDef) -> Tuple[List[Callsite], List[str]]:
        """Find all call sites of a function."""
        new_type = self.get_trivial_type(func)

        collector_plugin = SuggestionPlugin(func.fullname())

        self.plugin._plugins.insert(0, collector_plugin)
        try:
            errors = self.try_type(func, new_type)
        finally:
            self.plugin._plugins.pop(0)

        return collector_plugin.mystery_hits, errors

    def filter_options(self, guesses: List[CallableType],
                       is_method: bool) -> List[CallableType]:
        """Apply any configured filters to the possible guesses.

        Currently the only option is filtering based on Any prevalance."""
        return [
            t for t in guesses if self.flex_any is None
            or any_score_callable(t, is_method) >= self.flex_any
        ]

    def find_best(self, func: FuncDef,
                  guesses: List[CallableType]) -> Tuple[CallableType, int]:
        """From a list of possible function types, find the best one.

        For best, we want the fewest errors, then the best "score" from score_callable.
        """
        if not guesses:
            raise SuggestionFailure("No guesses that match criteria!")
        errors = {guess: self.try_type(func, guess) for guess in guesses}
        best = min(guesses,
                   key=lambda s:
                   (count_errors(errors[s]), self.score_callable(s)))
        return best, count_errors(errors[best])

    def get_suggestion(self, mod: str, node: FuncDef) -> PyAnnotateSignature:
        """Compute a suggestion for a function.

        Return the type and whether the first argument should be ignored.
        """
        graph = self.graph
        callsites, orig_errors = self.get_callsites(node)

        if self.no_errors and orig_errors:
            raise SuggestionFailure("Function does not typecheck.")

        is_method = bool(node.info) and not node.is_static

        if len(node.arg_names) >= 10:
            raise SuggestionFailure("Too many arguments")

        with strict_optional_set(graph[mod].options.strict_optional):
            guesses = self.get_guesses(
                is_method, self.get_trivial_type(node),
                self.get_default_arg_types(graph[mod], node), callsites)
        guesses = self.filter_options(guesses, is_method)
        if len(guesses) > self.max_guesses:
            raise SuggestionFailure("Too many possibilities!")
        best, _ = self.find_best(node, guesses)

        # Now try to find the return type!
        self.try_type(node, best)
        returns = get_return_types(self.manager.all_types, node)
        with strict_optional_set(graph[mod].options.strict_optional):
            if returns:
                ret_types = generate_type_combinations(returns)
            else:
                ret_types = [NoneType()]

        guesses = [best.copy_modified(ret_type=t) for t in ret_types]
        guesses = self.filter_options(guesses, is_method)
        best, errors = self.find_best(node, guesses)

        if self.no_errors and errors:
            raise SuggestionFailure("No annotation without errors")

        return self.pyannotate_signature(mod, is_method, best)

    def format_args(self, arg_kinds: List[List[int]],
                    arg_names: List[List[Optional[str]]],
                    arg_types: List[List[Type]]) -> str:
        args = []  # type: List[str]
        for i in range(len(arg_types)):
            for kind, name, typ in zip(arg_kinds[i], arg_names[i],
                                       arg_types[i]):
                arg = self.format_type(None, typ)
                if kind == ARG_STAR:
                    arg = '*' + arg
                elif kind == ARG_STAR2:
                    arg = '**' + arg
                elif kind in (ARG_NAMED, ARG_NAMED_OPT):
                    if name:
                        arg = "%s=%s" % (name, arg)
            args.append(arg)
        return "(%s)" % (", ".join(args))

    def find_node(self, key: str) -> Tuple[str, str, FuncDef]:
        """From a target name, return module/target names and the func def.

        The 'key' argument can be in one of two formats:
        * As the function full name, e.g., package.module.Cls.method
        * As the function location as file and line separated by column,
          e.g., path/to/file.py:42
        """
        # TODO: Also return OverloadedFuncDef -- currently these are ignored.
        node = None  # type: Optional[SymbolNode]
        if ':' in key:
            if key.count(':') > 1:
                raise SuggestionFailure(
                    'Malformed location for function: {}. Must be either'
                    ' package.module.Class.method or path/to/file.py:line'.
                    format(key))
            file, line = key.split(':')
            if not line.isdigit():
                raise SuggestionFailure(
                    'Line number must be a number. Got {}'.format(line))
            line_number = int(line)
            modname, node = self.find_node_by_file_and_line(file, line_number)
            tail = node.fullname()[len(modname) +
                                   1:]  # add one to account for '.'
        else:
            target = split_target(self.fgmanager.graph, key)
            if not target:
                raise SuggestionFailure("Cannot find module for %s" % (key, ))
            modname, tail = target
            node = self.find_node_by_module_and_name(modname, tail)

        if isinstance(node, Decorator):
            node = self.extract_from_decorator(node)
            if not node:
                raise SuggestionFailure(
                    "Object %s is a decorator we can't handle" % key)

        if not isinstance(node, FuncDef):
            raise SuggestionFailure("Object %s is not a function" % key)

        return modname, tail, node

    def find_node_by_module_and_name(self, modname: str,
                                     tail: str) -> Optional[SymbolNode]:
        """Find symbol node by module id and qualified name.

        Raise SuggestionFailure if can't find one.
        """
        tree = self.ensure_loaded(self.fgmanager.graph[modname])

        # N.B. This is reimplemented from update's lookup_target
        # basically just to produce better error messages.

        names = tree.names  # type: SymbolTable

        # Look through any classes
        components = tail.split('.')
        for i, component in enumerate(components[:-1]):
            if component not in names:
                raise SuggestionFailure(
                    "Unknown class %s.%s" %
                    (modname, '.'.join(components[:i + 1])))
            node = names[component].node  # type: Optional[SymbolNode]
            if not isinstance(node, TypeInfo):
                raise SuggestionFailure(
                    "Object %s.%s is not a class" %
                    (modname, '.'.join(components[:i + 1])))
            names = node.names

        # Look for the actual function/method
        funcname = components[-1]
        if funcname not in names:
            key = modname + '.' + tail
            raise SuggestionFailure(
                "Unknown %s %s" %
                ("method" if len(components) > 1 else "function", key))
        return names[funcname].node

    def find_node_by_file_and_line(self, file: str,
                                   line: int) -> Tuple[str, SymbolNode]:
        """Find symbol node by path to file and line number.

        Return module id and the node found. Raise SuggestionFailure if can't find one.
        """
        if not any(file.endswith(ext) for ext in PYTHON_EXTENSIONS):
            raise SuggestionFailure('Source file is not a Python file')
        try:
            modname, _ = self.finder.crawl_up(os.path.normpath(file))
        except InvalidSourceList:
            raise SuggestionFailure('Invalid source file name: ' + file)
        if modname not in self.graph:
            raise SuggestionFailure('Unknown module: ' + modname)
        # We must be sure about any edits in this file as this might affect the line numbers.
        tree = self.ensure_loaded(self.fgmanager.graph[modname], force=True)
        node = None  # type: Optional[SymbolNode]
        for _, sym, _ in tree.local_definitions():
            if isinstance(sym.node, FuncDef) and sym.node.line == line:
                node = sym.node
                break
            elif isinstance(sym.node,
                            Decorator) and sym.node.func.line == line:
                node = sym.node
                break
            # TODO: add support for OverloadedFuncDef.
        if not node:
            raise SuggestionFailure(
                'Cannot find a function at line {}'.format(line))
        return modname, node

    def extract_from_decorator(self, node: Decorator) -> Optional[FuncDef]:
        for dec in node.decorators:
            typ = None
            if (isinstance(dec, RefExpr) and isinstance(dec.node, FuncDef)):
                typ = dec.node.type
            elif (isinstance(dec, CallExpr)
                  and isinstance(dec.callee, RefExpr)
                  and isinstance(dec.callee.node, FuncDef)
                  and isinstance(dec.callee.node.type, CallableType)):
                typ = get_proper_type(dec.callee.node.type.ret_type)

            if not isinstance(typ, FunctionLike):
                return None
            for ct in typ.items():
                if not (len(ct.arg_types) == 1
                        and isinstance(ct.arg_types[0], TypeVarType)
                        and ct.arg_types[0] == ct.ret_type):
                    return None

        return node.func

    def try_type(self, func: FuncDef, typ: ProperType) -> List[str]:
        """Recheck a function while assuming it has type typ.

        Return all error messages.
        """
        old = func.unanalyzed_type
        # During reprocessing, unanalyzed_type gets copied to type (by aststrip).
        # We don't modify type because it isn't necessary and it
        # would mess up the snapshotting.
        func.unanalyzed_type = typ
        try:
            res = self.fgmanager.trigger(func.fullname())
            # if res:
            #     print('===', typ)
            #     print('\n'.join(res))
            return res
        finally:
            func.unanalyzed_type = old

    def reload(self, state: State, check_errors: bool = False) -> List[str]:
        """Recheck the module given by state.

        If check_errors is true, raise an exception if there are errors.
        """
        assert state.path is not None
        res = self.fgmanager.update([(state.id, state.path)], [])
        if check_errors and res:
            # TODO: apply color and formatting to error messages?
            raise SuggestionFailure("Error while processing %s:\n" % state.id +
                                    '\n'.join(res))
        return res

    def ensure_loaded(self, state: State, force: bool = False) -> MypyFile:
        """Make sure that the module represented by state is fully loaded."""
        if not state.tree or state.tree.is_cache_skeleton or force:
            self.reload(state, check_errors=True)
        assert state.tree is not None
        return state.tree

    def builtin_type(self, s: str) -> Instance:
        return self.manager.semantic_analyzer.builtin_type(s)

    def json_suggestion(self, mod: str, func_name: str, node: FuncDef,
                        suggestion: PyAnnotateSignature) -> str:
        """Produce a json blob for a suggestion suitable for application by pyannotate."""
        # pyannotate irritatingly drops class names for class and static methods
        if node.is_class or node.is_static:
            func_name = func_name.split('.', 1)[-1]

        # pyannotate works with either paths relative to where the
        # module is rooted or with absolute paths. We produce absolute
        # paths because it is simpler.
        path = os.path.abspath(self.graph[mod].xpath)

        obj = {
            'signature': suggestion,
            'line': node.line,
            'path': path,
            'func_name': func_name,
            'samples': 0
        }
        return json.dumps([obj], sort_keys=True)

    def pyannotate_signature(self, cur_module: Optional[str], is_method: bool,
                             typ: CallableType) -> PyAnnotateSignature:
        """Format a callable type as a pyannotate dict"""
        start = int(is_method)
        return {
            'arg_types':
            [self.format_type(cur_module, t) for t in typ.arg_types[start:]],
            'return_type':
            self.format_type(cur_module, typ.ret_type),
        }

    def format_signature(self, sig: PyAnnotateSignature) -> str:
        """Format a callable type in a way suitable as an annotation... kind of"""
        return "({}) -> {}".format(", ".join(sig['arg_types']),
                                   sig['return_type'])

    def format_type(self, cur_module: Optional[str], typ: Type) -> str:
        if self.use_fixme and isinstance(get_proper_type(typ), AnyType):
            return self.use_fixme
        return typ.accept(TypeFormatter(cur_module, self.graph))

    def score_type(self, t: Type, arg_pos: bool) -> int:
        """Generate a score for a type that we use to pick which type to use.

        Lower is better, prefer non-union/non-any types. Don't penalize optionals.
        """
        t = get_proper_type(t)
        if isinstance(t, AnyType):
            return 20
        if arg_pos and isinstance(t, NoneType):
            return 20
        if isinstance(t, UnionType):
            if any(isinstance(x, AnyType) for x in t.items):
                return 20
            if not is_optional(t):
                return 10
        if isinstance(t, CallableType) and (has_any_type(t)
                                            or is_tricky_callable(t)):
            return 10
        if self.try_text and isinstance(
                t, Instance) and t.type.fullname() == 'builtins.str':
            return 1
        return 0

    def score_callable(self, t: CallableType) -> int:
        return (sum([self.score_type(x, arg_pos=True) for x in t.arg_types]) +
                self.score_type(t.ret_type, arg_pos=False))
Ejemplo n.º 8
0
def find_sources_in_dir(finder: SourceFinder,
                        f: str) -> List[Tuple[str, Optional[str]]]:
    return normalise_build_source_list(
        finder.find_sources_in_dir(os.path.abspath(f)))
Ejemplo n.º 9
0
def crawl(finder: SourceFinder, f: str) -> Tuple[str, str]:
    module, base_dir = finder.crawl_up(f)
    return module, normalise_path(base_dir)
Ejemplo n.º 10
0
def process_options(
    args: List[str],
    require_targets: bool = True,
    server_options: bool = False,
    fscache: Optional[FileSystemCache] = None,
) -> Tuple[List[BuildSource], Options]:
    """Parse command line arguments."""

    parser = argparse.ArgumentParser(prog='mypy',
                                     epilog=FOOTER,
                                     fromfile_prefix_chars='@',
                                     formatter_class=AugmentedHelpFormatter)

    strict_flag_names = []  # type: List[str]
    strict_flag_assignments = []  # type: List[Tuple[str, bool]]

    def add_invertible_flag(flag: str,
                            *,
                            inverse: Optional[str] = None,
                            default: bool,
                            dest: Optional[str] = None,
                            help: str,
                            strict_flag: bool = False) -> None:
        if inverse is None:
            inverse = invert_flag_name(flag)

        if help is not argparse.SUPPRESS:
            help += " (inverse: {})".format(inverse)

        arg = parser.add_argument(
            flag,
            action='store_false' if default else 'store_true',
            dest=dest,
            help=help)
        dest = arg.dest
        arg = parser.add_argument(
            inverse,
            action='store_true' if default else 'store_false',
            dest=dest,
            help=argparse.SUPPRESS)
        if strict_flag:
            assert dest is not None
            strict_flag_names.append(flag)
            strict_flag_assignments.append((dest, not default))

    # Unless otherwise specified, arguments will be parsed directly onto an
    # Options object.  Options that require further processing should have
    # their `dest` prefixed with `special-opts:`, which will cause them to be
    # parsed into the separate special_opts namespace object.
    parser.add_argument('-v',
                        '--verbose',
                        action='count',
                        dest='verbosity',
                        help="more verbose messages")
    parser.add_argument('-V',
                        '--version',
                        action='version',
                        version='%(prog)s ' + __version__)
    parser.add_argument('--python-version',
                        type=parse_version,
                        metavar='x.y',
                        help='use Python x.y',
                        dest='special-opts:python_version')
    parser.add_argument(
        '--python-executable',
        action='store',
        metavar='EXECUTABLE',
        help="Python executable used for finding PEP 561 compliant installed"
        " packages and stubs",
        dest='special-opts:python_executable')
    parser.add_argument(
        '--no-site-packages',
        action='store_true',
        dest='special-opts:no_executable',
        help="Do not search for installed PEP 561 compliant packages")
    parser.add_argument(
        '--platform',
        action='store',
        metavar='PLATFORM',
        help="typecheck special-cased code for the given OS platform "
        "(defaults to sys.platform)")
    parser.add_argument('-2',
                        '--py2',
                        dest='python_version',
                        action='store_const',
                        const=defaults.PYTHON2_VERSION,
                        help="use Python 2 mode")
    parser.add_argument('--ignore-missing-imports',
                        action='store_true',
                        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-unimported',
        default=False,
        action='store_true',
        help="disallow Any types resulting from unfollowed imports")
    parser.add_argument('--disallow-any-expr',
                        default=False,
                        action='store_true',
                        help='disallow all expressions that have type Any')
    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")
    add_invertible_flag(
        '--disallow-untyped-defs',
        default=False,
        strict_flag=True,
        help="disallow defining functions without type annotations"
        " or with incomplete type annotations")
    add_invertible_flag(
        '--disallow-incomplete-defs',
        default=False,
        strict_flag=True,
        help="disallow defining functions with incomplete type annotations")
    add_invertible_flag(
        '--check-untyped-defs',
        default=False,
        strict_flag=True,
        help="type check the interior of functions without type annotations")
    add_invertible_flag(
        '--disallow-subclassing-any',
        default=False,
        strict_flag=True,
        help="disallow subclassing values of type 'Any' when defining classes")
    add_invertible_flag(
        '--warn-incomplete-stub',
        default=False,
        help="warn if missing type annotation in typeshed, only relevant with"
        " --check-untyped-defs enabled")
    add_invertible_flag(
        '--disallow-untyped-decorators',
        default=False,
        strict_flag=True,
        help="disallow decorating typed functions with untyped decorators")
    add_invertible_flag(
        '--warn-redundant-casts',
        default=False,
        strict_flag=True,
        help="warn about casting an expression to its inferred type")
    add_invertible_flag(
        '--no-warn-no-return',
        dest='warn_no_return',
        default=True,
        help="do not warn about functions that end without returning")
    add_invertible_flag('--warn-return-any',
                        default=False,
                        strict_flag=True,
                        help="warn about returning values of type Any"
                        " from non-Any typed functions")
    add_invertible_flag('--warn-unused-ignores',
                        default=False,
                        strict_flag=True,
                        help="warn about unneeded '# type: ignore' comments")
    add_invertible_flag(
        '--warn-unused-configs',
        default=False,
        strict_flag=True,
        help="warn about unused '[mypy-<pattern>]' config sections")
    add_invertible_flag(
        '--show-error-context',
        default=False,
        dest='show_error_context',
        help='Precede errors with "note:" messages explaining context')
    add_invertible_flag(
        '--no-implicit-optional',
        default=False,
        strict_flag=True,
        help="don't assume arguments with default values of None are Optional")
    parser.add_argument('-i',
                        '--incremental',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_argument('--no-incremental',
                        action='store_false',
                        dest='incremental',
                        help="disable module cache, (inverse: --incremental)")
    parser.add_argument('--quick-and-dirty',
                        action='store_true',
                        help="use cache even if dependencies out of date "
                        "(implies --incremental)")
    parser.add_argument(
        '--cache-dir',
        action='store',
        metavar='DIR',
        help="store module cache info in the given folder in incremental mode "
        "(defaults to '{}')".format(defaults.CACHE_DIR))
    parser.add_argument(
        '--cache-fine-grained',
        action='store_true',
        help="include fine-grained dependency information in the cache")
    parser.add_argument('--skip-version-check',
                        action='store_true',
                        help="allow using cache written by older mypy version")
    parser.add_argument('--strict-optional',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_argument(
        '--no-strict-optional',
        action='store_false',
        dest='strict_optional',
        help="disable strict Optional checks (inverse: --strict-optional)")
    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(
        '--always-true',
        metavar='NAME',
        action='append',
        default=[],
        help="Additional variable to be considered True (may be repeated)")
    parser.add_argument(
        '--always-false',
        metavar='NAME',
        action='append',
        default=[],
        help="Additional variable to be considered False (may be repeated)")
    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',
                        help="show traceback on fatal error")
    parser.add_argument('--stats',
                        action='store_true',
                        dest='dump_type_stats',
                        help="dump stats")
    parser.add_argument('--inferstats',
                        action='store_true',
                        dest='dump_inference_stats',
                        help="dump type inference stats")
    parser.add_argument('--custom-typing',
                        metavar='MODULE',
                        dest='custom_typing_module',
                        help="use a custom typing module")
    parser.add_argument('--custom-typeshed-dir',
                        metavar='DIR',
                        help="use the custom typeshed in DIR")
    parser.add_argument('--scripts-are-modules',
                        action='store_true',
                        help="Script x becomes module x instead of __main__")
    parser.add_argument('--config-file',
                        help="Configuration file, must have a [mypy] section "
                        "(defaults to {})".format(', '.join(
                            defaults.CONFIG_FILES)))
    add_invertible_flag('--show-column-numbers',
                        default=False,
                        help="Show column numbers in error messages")
    parser.add_argument(
        '--find-occurrences',
        metavar='CLASS.MEMBER',
        dest='special-opts:find_occurrences',
        help="print out all usages of a class member (experimental)")
    strict_help = "Strict mode. Enables the following flags: {}".format(
        ", ".join(strict_flag_names))
    parser.add_argument('--strict',
                        action='store_true',
                        dest='special-opts:strict',
                        help=strict_help)
    parser.add_argument(
        '--shadow-file',
        nargs=2,
        metavar=('SOURCE_FILE', 'SHADOW_FILE'),
        dest='shadow_file',
        action='append',
        help="When encountering SOURCE_FILE, read and typecheck "
        "the contents of SHADOW_FILE instead.")
    # hidden options
    # --debug-cache will disable any cache-related compressions/optimizations,
    # which will make the cache writing process output pretty-printed JSON (which
    # is easier to debug).
    parser.add_argument('--debug-cache',
                        action='store_true',
                        help=argparse.SUPPRESS)
    # --dump-deps will dump all fine-grained dependencies to stdout
    parser.add_argument('--dump-deps',
                        action='store_true',
                        help=argparse.SUPPRESS)
    # --dump-graph will dump the contents of the graph of SCCs and exit.
    parser.add_argument('--dump-graph',
                        action='store_true',
                        help=argparse.SUPPRESS)
    # --semantic-analysis-only does exactly that.
    parser.add_argument('--semantic-analysis-only',
                        action='store_true',
                        help=argparse.SUPPRESS)
    # --local-partial-types disallows partial types spanning module top level and a function
    # (implicitly defined in fine-grained incremental mode)
    parser.add_argument('--local-partial-types',
                        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',
                        dest='special-opts:dirty_stubs',
                        help=argparse.SUPPRESS)
    parser.add_argument('--use-python-path',
                        action='store_true',
                        dest='special-opts:use_python_path',
                        help=argparse.SUPPRESS)
    parser.add_argument('-s',
                        '--silent-imports',
                        action='store_true',
                        dest='special-opts:silent_imports',
                        help=argparse.SUPPRESS)
    parser.add_argument('--almost-silent',
                        action='store_true',
                        dest='special-opts:almost_silent',
                        help=argparse.SUPPRESS)
    parser.add_argument('--fast-parser',
                        action='store_true',
                        dest='special-opts:fast_parser',
                        help=argparse.SUPPRESS)
    parser.add_argument('--no-fast-parser',
                        action='store_true',
                        dest='special-opts:no_fast_parser',
                        help=argparse.SUPPRESS)
    if server_options:
        # TODO: This flag is superfluous; remove after a short transition (2018-03-16)
        parser.add_argument('--experimental',
                            action='store_true',
                            dest='fine_grained_incremental',
                            help="enable fine-grained incremental mode")
        parser.add_argument(
            '--use-fine-grained-cache',
            action='store_true',
            help="use the cache in fine-grained incremental mode")

    report_group = parser.add_argument_group(
        title='report generation',
        description='Generate a report in the specified format.')
    for report_type in sorted(reporter_classes):
        report_group.add_argument('--%s-report' %
                                  report_type.replace('_', '-'),
                                  metavar='DIR',
                                  dest='special-opts:%s_report' % report_type)

    code_group = parser.add_argument_group(
        title='How to specify the code to type check')
    code_group.add_argument(
        '-m',
        '--module',
        action='append',
        metavar='MODULE',
        default=[],
        dest='special-opts:modules',
        help="type-check module; can repeat for more modules")
    code_group.add_argument(
        '-p',
        '--package',
        action='append',
        metavar='PACKAGE',
        default=[],
        dest='special-opts:packages',
        help="type-check package recursively; can be repeated")
    code_group.add_argument('-c',
                            '--command',
                            action='append',
                            metavar='PROGRAM_TEXT',
                            dest='special-opts:command',
                            help="type-check program passed in as string")
    code_group.add_argument('-F',
                            '--filename',
                            action='store',
                            metavar='FILENAME',
                            dest='special-opts:filename',
                            help="filename for type-check program")
    code_group.add_argument('-P',
                            '--sys-path',
                            action='append',
                            metavar='SYS_PATH',
                            dest='special-opts:sys_path',
                            help="extra sys.path() to add")
    code_group.add_argument(metavar='files',
                            nargs='*',
                            dest='special-opts:files',
                            help="type-check given files or directories")

    # Parse arguments once into a dummy namespace so we can get the
    # filename for the config file and know if the user requested all strict options.
    dummy = argparse.Namespace()
    parser.parse_args(args, dummy)
    config_file = dummy.config_file
    if config_file is not None and not os.path.exists(config_file):
        parser.error("Cannot find config file '%s'" % config_file)

    # Parse config file first, so command line can override.
    options = Options()
    parse_config_file(options, config_file)

    # Set strict flags before parsing (if strict mode enabled), so other command
    # line options can override.
    if getattr(dummy, 'special-opts:strict'):
        for dest, value in strict_flag_assignments:
            setattr(options, dest, value)

    # Parse command line for real, using a split namespace.
    special_opts = argparse.Namespace()
    parser.parse_args(args,
                      SplitNamespace(options, special_opts, 'special-opts:'))

    # --use-python-path is no longer supported; explain why.
    if special_opts.use_python_path:
        parser.error(
            "Sorry, --use-python-path is no longer supported.\n"
            "If you are trying this because your code depends on a library module,\n"
            "you should really investigate how to obtain stubs for that module.\n"
            "See https://github.com/python/mypy/issues/1411 for more discussion."
        )

    sys_path = []
    for sp in special_opts.sys_path:
        sys_path.extend(sp.split(os.pathsep))
    options.sys_path = sys_path

    # 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)
    if special_opts.almost_silent:
        print(
            "Warning: --almost-silent has been replaced by "
            "--follow-imports=errors",
            file=sys.stderr)
        if options.follow_imports == 'normal':
            options.follow_imports = 'errors'
    elif special_opts.silent_imports:
        print(
            "Warning: --silent-imports has been replaced by "
            "--ignore-missing-imports --follow-imports=skip",
            file=sys.stderr)
        options.ignore_missing_imports = True
        if options.follow_imports == 'normal':
            options.follow_imports = 'skip'
    if special_opts.dirty_stubs:
        print(
            "Warning: -f/--dirty-stubs is deprecated and no longer necessary. Mypy no longer "
            "checks the git status of stubs.",
            file=sys.stderr)
    if special_opts.fast_parser:
        print("Warning: --fast-parser is now the default (and only) parser.")
    if special_opts.no_fast_parser:
        print(
            "Warning: --no-fast-parser no longer has any effect.  The fast parser "
            "is now mypy's default and only parser.")

    try:
        infer_python_version_and_executable(options, special_opts)
    except PythonExecutableInferenceError as e:
        parser.error(str(e))

    if special_opts.no_executable:
        options.python_executable = None

    # Check for invalid argument combinations.
    if require_targets:
        code_methods = sum(
            bool(c) for c in [
                special_opts.modules +
                special_opts.packages, special_opts.command, special_opts.files
            ])
        if code_methods == 0:
            parser.error("Missing target module, package, files, or command.")
        elif code_methods > 1:
            parser.error(
                "May only specify one of: module/package, files, or command.")

    # Check for overlapping `--always-true` and `--always-false` flags.
    overlap = set(options.always_true) & set(options.always_false)
    if overlap:
        parser.error(
            "You can't make a variable always true and always false (%s)" %
            ', '.join(sorted(overlap)))

    # Set build flags.
    if options.strict_optional_whitelist is not None:
        # TODO: Deprecate, then kill this flag
        options.strict_optional = True
    if special_opts.find_occurrences:
        experiments.find_occurrences = special_opts.find_occurrences.split('.')
        assert experiments.find_occurrences is not None
        if len(experiments.find_occurrences) < 2:
            parser.error("Can only find occurrences of class members.")
        if len(experiments.find_occurrences) != 2:
            parser.error(
                "Can only find occurrences of non-nested class members.")

    # Set reports.
    for flag, val in vars(special_opts).items():
        if flag.endswith('_report') and val is not None:
            report_type = flag[:-7].replace('_', '-')
            report_dir = val
            options.report_dirs[report_type] = report_dir

    # Let quick_and_dirty imply incremental.
    if options.quick_and_dirty:
        options.incremental = True

    # Set target.
    if special_opts.modules + special_opts.packages:
        options.build_type = BuildType.MODULE
        lib_path = [os.getcwd()] + build.mypy_path()
        targets = []
        # TODO: use the same cache that the BuildManager will
        cache = build.FindModuleCache(fscache)
        for p in special_opts.packages:
            if os.sep in p or os.altsep and os.altsep in p:
                fail("Package name '{}' cannot have a slash in it.".format(p))
            p_targets = cache.find_modules_recursive(p, tuple(lib_path),
                                                     options.python_executable)
            if not p_targets:
                fail("Can't find package '{}'".format(p))
            targets.extend(p_targets)
        for m in special_opts.modules:
            targets.append(BuildSource(None, m, None))
        return targets, options
    elif special_opts.command:
        options.build_type = BuildType.PROGRAM_TEXT
        if special_opts.filename:
            finder = SourceFinder(fscache)
            name, base_dir = finder.crawl_up(
                os.path.normpath(special_opts.filename))
        else:
            name, base_dir = None, None
        targets = [
            BuildSource(special_opts.filename, name,
                        '\n'.join(special_opts.command), base_dir)
        ]
        return targets, options
    else:
        try:
            targets = create_source_list(special_opts.files, options, fscache)
        except InvalidSourceList as e:
            fail(str(e))
        return targets, options