Skip to content

Spurious type error using result of re.match in lambda inside map #18947

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

Open
alexcoplan opened this issue Apr 21, 2025 · 3 comments
Open

Spurious type error using result of re.match in lambda inside map #18947

alexcoplan opened this issue Apr 21, 2025 · 3 comments
Labels
bug mypy got something wrong topic-type-context Type context / bidirectional inference

Comments

@alexcoplan
Copy link

Consider the following testcase:

import re
r = re.compile("([a-z]+)")
if m := re.match(r, "abc"):
  matches = map(lambda i: m.group(i), [1])
  print(next(matches))

I expect this to pass type checking successfully, but mypy instead complains about the map line as follows:

mypy ./t.py
t.py:4: error: Item "None" of "Match[str] | None" has no attribute "group"  [union-attr]
Found 1 error in 1 file (checked 1 source file)

The bug only seems to happen if the lambda appears directly inside the call to map. E.g. if I pre-declare it as follows:

import re
r = re.compile("([a-z]+)")
if m := re.match(r, "abc"):
  fn = lambda i: m.group(i)
  matches = map(fn, [1])
  print(next(matches))

then this type-checks OK. My environment is as follows:

$ mypy --version
mypy 1.15.0 (compiled: yes)
$ python3 --version
Python 3.13.2
@alexcoplan alexcoplan added the bug mypy got something wrong label Apr 21, 2025
@brianschubert brianschubert added the topic-type-context Type context / bidirectional inference label Apr 21, 2025
@A5rocks
Copy link
Collaborator

A5rocks commented Apr 21, 2025

Hmm my first instinct is that this is intentional because mypy tries not to persist type narrowing into callbacks (which it cannot track the lifetimes of). But moving the lambda out wouldn't fix this in that case...

@erictraut
Copy link

If you move this code within a function, it will type check without an error.

def func():
    r = re.compile("([a-z]+)")
    if m := re.match(r, "abc"):
        matches = map(lambda i: m.group(i), [1])

In the original sample, the code is in the global scope, and it's not safe for a type checker to assume that m retains its narrowed value within the lambda because variables in the global scope can be modified outside of the module. Variables within a local scope cannot be modified outside of the local scope, so a type checker can prove that m is not reassigned. Pyright makes the same assumption here. So I don't think this is a bug.

I'm not sure why mypy retains the narrowed type when the lambda is moved outside of the call. I don't see how that makes it safe from a type perspective. This could be a bug.

@sterliakov
Copy link
Collaborator

Yes, what Eric mentions above is indeed a bug, lambda return expr is handled in a wrong scope.

x: int | None
if x is not None:
    bad = lambda: reveal_type(x)  # N: Revealed type is "builtins.int"
    def bad_fn():
        reveal_type(x)  # N: Revealed type is "Union[builtins.int, None]"
        return -x  # E: Unsupported operand type for unary - ("int | None")  [operator]

Currently def functions use a new instance of binder and copy everything "safe" from global scope into it, and lambdas do not undergo similar treatment. This is a fairly difficult problem to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-context Type context / bidirectional inference
Projects
None yet
Development

No branches or pull requests

5 participants