python / python/mypy

Multi-level type resolution

Open
#10,164 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature priority-2-low
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Feature

It would be nice if Mypy was able to resolve the types in the below snippet:

import os
import typing as t
T = t.TypeVar('T')
R = t.TypeVar('R')

class flatmap(t.Generic[T, R]):

  def __init__(self, func: t.Callable[[T], R]) -> None:
    self.func = func

  def __ror__(self, value: t.Optional[T]) -> t.Optional[R]:
    if value is not None:
      return self.func(value)
    return None

# error: Cannot infer type argument 1 of "flatmap"
# error: "object" has no attribute "upper"
print(os.getenv('VARNAME') | flatmap(lambda m: m.upper()))
print(os.getenv('VARNAME') | flatmap(str.upper))  # ok

Pitch

Looking at just flatmap(lambda m: m.upper()), neither T nor R can be immediately determined.
However, it should be possible to deduce the type variables by taking into account the broader
context.

  • T = str can be derived from the __ror__() call
  • After propagating T into the flatmap() and lambda, R = str can be deduced from the lambda body

One potential use case is already highlighted in the example above. Being able to chain the flatmap() function using ror adds a lot of readability compared to a normal call:

# 1)
os.getenv('VARNAME') | flatmap(lambda m: m.upper())

# 2)
flatmap(os.getenv('VARNAME'), lambda m: m.upper())

The obvious workaround in this example would be to use str.upper which would satisfy Mypy because the function is already typed. However, there are other example where the there is no already-typed alternative, like accessing an attribute of an optional object. Nr. 1 would be preferred over 2 for it's better readability. 3 is an alternative without a helper function, though that also gets harder to read as you start to work with more optional values.

# 1)
email = maybe_get_user() | flatmap(lambda user: user.email) | flatmap(sanitize_email_address)

# 2)
email = flatmap(flatmap(maybe_get_user(), lambda user: user.email), sanitize_email_address)

# 3)
user = maybe_get_user()
email = sanitize_email_address(user.email) if user and user.email else None

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

Reproduce the issue with the provided Python snippet and compare the lambda case with the working str.upper case. Trace mypy's handling of generic flatmap construction, the ror call, and propagation through the lambda; done means the examples infer the expected Optional[str] results without errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.