Skip to content
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

Multi-level type resolution #10164

Open
NiklasRosenstein opened this issue Mar 5, 2021 · 1 comment
Open

Multi-level type resolution #10164

NiklasRosenstein opened this issue Mar 5, 2021 · 1 comment

Comments

@NiklasRosenstein
Copy link

NiklasRosenstein commented Mar 5, 2021

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
@JukkaL
Copy link
Collaborator

JukkaL commented Mar 5, 2021

This would be nice to support, but it would require a major redesign of the type inference engine used in mypy. Currently mypy performs type inference essentially one expression at a time, and here we'd need to build constraints for multiple expressions and solve them together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants