Search code examples
python-3.xtypesstatic-typingmypy

Let mypy warn about equality checks of variables with different type


mypy --strict allows the following minimal example without any error:

a: int = 1
b: str = '1'

if a == b:
    pass
else:
    pass

Is there a possibility to make it issue an error (or at least a warning) about the if a == b: line?


Solution

  • Edit: My original answer below described how to implement this by writing a custom mypy plugin.

    However, as of mypy 0.700, this is now possible to do directly via the --strict-equality flag. Note that as of time of writing, this flag is not enabled by default via the --strict flag.

    For example, running mypy on the original program above will produce the following error:

    test.py:4: error: Non-overlapping equality check (left operand type: "int", right operand type: "str")
    

    You can find more details about this flag near the bottom of the Miscellaneous strictness options section of the mypy command line flags doc.


    This is possible using the (currently experimental and undocumented) plugins API.

    In short, add the following file somewhere within your project:

    from typing import Callable, Optional, Type
    from mypy.plugin import MethodContext, Plugin
    from mypy.meet import is_overlapping_types
    
    class StrictEqualityPlugin(Plugin):
        def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], Type]]:
            if fullname.endswith('__eq__') or fullname.endswith('__ne__'):
                return strict_check_callback
    
    def strict_check_callback(ctx: MethodContext) -> Type:
        if len(ctx.arg_types) == 1 and len(ctx.arg_types[0]) == 1:
            # Note: Expressions of the form 'base_type == arg_type' get
            # translated into `base_type.__eq__(arg_type)`.
            base_type = ctx.type
            arg_type = ctx.arg_types[0][0]
    
            # Two types are overlapping if one of the types could potentially be the
            # same as or a subtype of the other.
            #
            # If you want something even stricter, add `from mypy.sametypes import is_same_type`
            # up at the top and call `is_same_type` instead of `is_overlapping_types`.
            if not is_overlapping_types(base_type, arg_type):
                ctx.api.msg.warn(
                    "The left and right operands have disjoint types ({} and {})".format(
                        ctx.api.msg.format(base_type),
                        ctx.api.msg.format(arg_type),
                    ), 
                    ctx.context)
        return ctx.default_return_type
    
    def plugin(mypy_version: str) -> Plugin:
        return StrictEqualityPlugin
    

    Let's assume that the name of this file is strict_equality_plugins.py.

    Then, within the top-level of your project, create a mypy.ini file. The file should, at the bare minimum, contain the following:

    [mypy]
    plugins = ./path/to/strict_equality_plugins.py
    

    Then, running mypy within your root project will produce errors like the following:

    foo.py:1: warning: The left and right operands have disjoint types ("int" and "str")

    Disclaimer: The plugin API of the mypy project is highly experimental -- I make no promises that this plugin will continue to work unmodified in future versions of mypy.