python / python/mypy

Class structural pattern matching always returns `Any` as type

Open
#17,589 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

I just hit this issue and I am not sure if it's is a problem of "join-vs-union" or if it is an actual bug.

I am returning a return's ResultE from a function and then I am parsing the result using match. The type of the values matched in the class structural matching always resolve to Any, even though mypy recognizes the type of the return value to have a specific combination of types:

Revealed type is "returns.result.Result[builtins.bool, builtins.Exception]"

To Reproduce

In an environment with returns installed:

from typing import reveal_type

from returns.result import Failure, Success, safe


@safe
def safe_function(value: int) -> bool:
    if (value % 2) == 0:
        raise Exception("It's not odd!")

    return True


def pattern_matcher() -> bool:
    function_out = safe_function(12)
    reveal_type(function_out)
    match function_out:
        case Success(result):
            reveal_type(result)
            return result
        case Failure(err):
            reveal_type(err)
            print(err)

    return False

Expected Behavior

Mypy validates the file correctly with the following output:

$ mypy match_test.py
match_test.py:16: note: Revealed type is "returns.result.Result[builtins.bool, builtins.Exception]"
match_test.py:19: note: Revealed type is "builtins.bool"
match_test.py:22: note: Revealed type is "builtins.Exception"
Success: no issues found in 1 source file

Actual Behavior

Mypy returns error with the following output:

$ mypy match_test.py
match_test.py:16: note: Revealed type is "returns.result.Result[builtins.bool, builtins.Exception]"
match_test.py:19: note: Revealed type is "Any"
match_test.py:20: error: Returning Any from function declared to return "bool"  [no-any-return]
match_test.py:22: note: Revealed type is "Any"
Found 1 error in 1 file (checked 1 source file)

Extra test

I changed the match pattern to this: case Success(bool(result)): and mypy did not return any error:

from typing import reveal_type

from returns.result import Failure, Success, safe


@safe
def safe_function(value: int) -> bool:
    if (value % 2) == 0:
        raise Exception("It's not odd!")

    return True


def pattern_matcher() -> bool:
    function_out = safe_function(12)
    reveal_type(function_out)
    match function_out:
        case Success(bool(result)):
            reveal_type(result)
            return result
        case Failure(err):
            reveal_type(err)
            print(err)

    return False

Output:

$ mypy match_test.py
match_test.py:16: note: Revealed type is "returns.result.Result[builtins.bool, builtins.Exception]"
match_test.py:19: note: Revealed type is "builtins.bool"
match_test.py:22: note: Revealed type is "Any"
Success: no issues found in 1 source file

Your Environment

  • Mypy version used: mypy 1.11.0 (compiled: yes)
  • Mypy configuration options from mypy.ini (and other config files):
    [tool.mypy]
    python_version = "3.12"
    mypy_path = "$MYPY_CONFIG_FILE_DIR/src"
    enable_incomplete_feature = ["NewGenericSyntax"]
    ignore_missing_imports = true
    no_implicit_optional = true
    strict_optional = true
    strict = true
    plugins = ["returns.contrib.mypy.returns_plugin"]
    disallow_untyped_defs = true
    
  • Python version used: Python 3.12.4
  • Returns version used: 0.23.0

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the supplied match_test.py reproducer with mypy 1.11.0 and compare the revealed types for Success(result) with Success(bool(result)). Trace the class structural pattern-matching type inference involved in those cases and add regression coverage. Done means the generic payload types are preserved, revealing bool and Exception instead of Any, without introducing errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.