Skip to content

Generic type parameter incorrectly instantiated to <nothing> #7836

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

Closed
cirodrig opened this issue Oct 31, 2019 · 2 comments
Closed

Generic type parameter incorrectly instantiated to <nothing> #7836

cirodrig opened this issue Oct 31, 2019 · 2 comments

Comments

@cirodrig
Copy link

Type inference incorrectly instantiates a type variable to <nothing> in the following example. It was run with mypy --python-version=3.7. Mypy version 0.720.

Observed behavior: In the function example, mypy reports an error in the call dict_value_substituter(str_id_substituter)(m, d) (that is, the second function call). It expects d to have type Dict[<nothing>, str] and its actual type is Dict[int, str]. Apparently, the type parameter of dict_value_substituter was instantiated to <nothing>.

Expected behavior: I expected the parameter to be instantiated to int. In the modified function example_fixed, the expected type is given explicitly and mypy accepts it.

from typing import Callable, TypeVar, Dict, List, Mapping
from typing_extensions import Protocol

B = TypeVar('B')
C = TypeVar('C')

# Protocol for a substituter that finds and replaces strings in a `B`.
# Given `s: Substituter[B]`, the call `s(m, x)` creates a modified copy
# of `x` where each `a: str` is replaced by `m[a]`.
class Substituter(Protocol[B]):
    def __call__(self, mapping: Mapping[str, str], target: B) -> B: ...

# Substituter for a single string
def str_id_substituter(mapping: Mapping[str, str], target: str) -> str:
    return mapping[target]

# Substituter for all values in a dictionary
def dict_value_substituter(s: Substituter[B]) -> Substituter[Dict[C, B]]:
    def apply(mapping: Mapping[str, str], target: Dict[C, B]) -> Dict[C, B]:
        return dict((k, s(mapping, v)) for k, v in target.items())
    return apply

# Inputs for example
d: Dict[int, str] = {1: 'a'}
m: Mapping[str, str] = {'a': 'b'}

def example() -> Dict[int, str]:
    # Wrong type inferred for return value of dict_value_substituter(...)
    return dict_value_substituter(str_id_substituter)(m, d)

def example_fixed() -> Dict[int, str]:
    # With explicit type annotation, it works
    s: Substituter[Dict[int, str]] = dict_value_substituter(str_id_substituter)
    return s(m, d)

print(example())
print(example_fixed())
@jirassimok
Copy link

This appears to be a variation of #3924, demonstrating that it covers classes implementing __call__, not just aliases of Callable.

Here is a simplified example:

from typing import Generic, TypeVar

T = TypeVar('T')

class SameTypeFn(Generic[T]):
    def __call__(self, x: T) -> T: ...

# Examine the constructor
reveal_type(SameTypeFn)   # def [T] () -> module.SameTypeFn[T`-1]
reveal_type(SameTypeFn()) # module.SameTypeFn[<nothing>]

@ilevkivskyi
Copy link
Member

Mypy is actually correct here. The type for dict_value_substituter() is bad (underspecified), because it has a type variable that appears only in the return type. We already have an issue for having a better error message for such cases, see #2885

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

No branches or pull requests

3 participants