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

Spurious error with constrained type variables in generic functions #5493

Closed
whoiscc opened this issue Aug 17, 2018 · 2 comments · Fixed by #5699
Closed

Spurious error with constrained type variables in generic functions #5493

whoiscc opened this issue Aug 17, 2018 · 2 comments · Fixed by #5699
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal topic-type-variables

Comments

@whoiscc
Copy link

whoiscc commented Aug 17, 2018

Reproduction:

from typing import TypeVar, List


class A:
    pass


class B(A):
    pass


class C:
    pass


LooseType = TypeVar('LooseType', A, B, C)
ListType = List[LooseType]


def foo(xs: ListType[LooseType]) -> None:
    pass


StrictType = TypeVar('StrictType', B, C)


def bar(xs: ListType[StrictType]) -> None:
    foo(xs)

This is denied with error reporting:

test.py:28: error: Argument 1 to "foo" has incompatible type "List[B]"; expected "List[A]"
test.py:28: note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
test.py:28: note: Consider using "Sequence" instead, which is covariant

Is this really not allowed? If B is not derived from A, then everything is ok.

In my original code, the container of A, B and C is Dict[List[Tuple[LooseType, str]]]. I cannot change List to Sequence because I need append method.

In addition, the notes disappear in complicated case.

@ilevkivskyi
Copy link
Member

Thanks for reporting!

Here is a simpler repro (that also is cleaner, since your original repro uses some questionable patterns):

class A:
    pass
class B(A):
    pass
class C:
    pass

LooseType = TypeVar('LooseType', A, B, C)
def foo(xs: List[LooseType]) -> LooseType:
    return xs[0]


StrictType = TypeVar('StrictType', B, C)
def bar(xs: List[StrictType]) -> StrictType:
    foo(xs)
    return xs[0]

It seems to me (but this is a pure guess). This is subtle combination of #4872 and how functions with restricted type variables are checked.

@ilevkivskyi ilevkivskyi added bug mypy got something wrong topic-type-variables priority-1-normal false-positive mypy gave an error on correct code labels Aug 17, 2018
@whoiscc
Copy link
Author

whoiscc commented Aug 17, 2018

Actually I uses StrictType and LooseType in such way:

from typing import TypeVar, List


class A:
    pass


class B(A):
    pass


class C:
    pass


LooseType = TypeVar('LooseType', A, B, C)
ListType = List[LooseType]


def foo(xs: ListType[LooseType]) -> LooseType:
    return xs[0]


StrictType = TypeVar('StrictType', B, C)


def bar(xs: ListType[StrictType]) -> StrictType:
    return foo(xs)

(Only last two lines are changed.)

And this causes one more error with inheritance:

test.py:28: error: Incompatible return value type (got "A", expected "B")

It does not exist neither if B is not derived from A. I thought this is an unrelevant issue and eliminated it from original snippet.

@ilevkivskyi ilevkivskyi changed the title Generic works incorrectly with invariant Spurious error with constrained type variables in generic functions Aug 17, 2018
ilevkivskyi added a commit that referenced this issue Oct 2, 2018
Fixes #4872 
Fixes #3876
Fixes #2678 
Fixes #5199 
Fixes #5493 
(It also fixes a bunch of similar issues previously closed as duplicates, except one, see below).

This PR fixes a problems when mypy commits to soon to using outer context for type inference. This is done by:
* Postponing inference to inner (argument) context in situations where type inferred from outer (return) context doesn't satisfy bounds or constraints.
* Adding a special case for situation where optional return is inferred against optional context. In such situation, unwrapping the optional is a better idea in 99% of cases. (Note: this doesn't affect type safety, only gives empirically more reasonable inferred types.)

In general, instead of adding a special case, it would be better to use inner and outer context at the same time, but this a big change (see comment in code), and using the simple special case fixes majority of issues. Among reported issues, only #5311 will stay unfixed.
TV4Fun pushed a commit to TV4Fun/mypy that referenced this issue Oct 4, 2018
Fixes python#4872 
Fixes python#3876
Fixes python#2678 
Fixes python#5199 
Fixes python#5493 
(It also fixes a bunch of similar issues previously closed as duplicates, except one, see below).

This PR fixes a problems when mypy commits to soon to using outer context for type inference. This is done by:
* Postponing inference to inner (argument) context in situations where type inferred from outer (return) context doesn't satisfy bounds or constraints.
* Adding a special case for situation where optional return is inferred against optional context. In such situation, unwrapping the optional is a better idea in 99% of cases. (Note: this doesn't affect type safety, only gives empirically more reasonable inferred types.)

In general, instead of adding a special case, it would be better to use inner and outer context at the same time, but this a big change (see comment in code), and using the simple special case fixes majority of issues. Among reported issues, only python#5311 will stay unfixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal topic-type-variables
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants