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

Empty collection's subtype evaluates to Unknown when in tuple passed to Iterable with TypeVar parameter #4244

Closed
Avasam opened this issue Nov 29, 2022 · 5 comments
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@Avasam
Copy link

Avasam commented Nov 29, 2022

Describe the bug
An empty set or list inside a tuple used as a function parameter typed as Iterable[_T] (where _T is a TypeVar) will result in reportUnknownArgumentType. This does not happen inside of lists. And does not happen if the empty collection is a tuple (likely because zero-length tuples are their own type). It also only happens with TypeVar.

To Reproduce

from collections.abc import Iterable
from typing import Any
from typing_extensions import TypeVar

_T = TypeVar("_T")
def foo(_: Iterable[Any]) -> None: ...
def bar(_: Iterable[_T]) -> _T: ...

foo(["A", "B", ()])
foo(["A", "B", []])
foo(["A", "B", {}])
foo(("A", "B", ()))
foo(("A", "B", []))
foo(("A", "B", {}))

bar(["A", "B", ()])
bar(["A", "B", []])
bar(["A", "B", {}])
bar(("A", "B", ()))
bar(("A", "B", []))  # error reportUnknownArgumentType
bar(("A", "B", {}))  # error reportUnknownArgumentType

Expected behavior
No error (empty set infered as dict[Any, Any] and empty list as list[Any]), like all the other calls

Screenshots or Code
image

VS Code extension or command-line
Pylance (v2022.11.30) and pyright (1.1.281), strict settings

@erictraut
Copy link
Collaborator

erictraut commented Nov 29, 2022

b1 = bar(["A", "B", ()])
b2 = bar(["A", "B", []]) # missing error (bug)
b3 = bar(["A", "B", {}]) # missing error (bug)
b4 = bar(("A", "B", ()))
b5 = bar(("A", "B", []))  # error reportUnknownArgumentType
b6 = bar(("A", "B", {}))  # error reportUnknownArgumentType

I think pyright is correct to report an "unknown argument type" for the expressions assigned to b5 and b6, so I don't consider that a bug.

However, I agree that there is an inconsistency here. The expressions b2 and b3 should also generate the same error because they also include an "unknown" type argument. This is a bug.

Calls to foo will not generate the same error because bidirectional type inference causes [] to be inferred as Iterable[Any] rather than list[Unknown]. Similarly, {} is inferred as Iterable[Any] rather than dict[Unknown, Unknown].

The expression () always evaluates to tuple[()] and is interpreted as Iterable[Any], so that will never trigger the "unknown" checks.

This will be addressed in the next release.

erictraut pushed a commit that referenced this issue Nov 29, 2022
…st or dict literal is included in a list expression with an "expected type" (i.e. bidirectional type inference is being attempted). This addresses #4244.
@erictraut erictraut added bug Something isn't working addressed in next version Issue is fixed and will appear in next published version labels Nov 29, 2022
@Avasam
Copy link
Author

Avasam commented Nov 29, 2022

Not the bug I thought but glad to have found an inconsistency nonetheless.

How would you recommend dealing with b2, b3, b5, b6?

Is a cast necessary here?
Should the empty array be extracted to a constant? (That doesn't seem right. I have no guarantee the method won't mutate it).
Kindof expected it to be acceptable since it's an anonymous empty array (meaning afaik the subtype does not matter). Kindof how typescript does never[].

@erictraut
Copy link
Collaborator

erictraut commented Nov 29, 2022

That depends on what type you expect bar(["A", "B", []]) to return. Do you want the return type _T to evaluate to str | list[str]? Or str | list[Any]? Or something else? Rather than using [], you could use list[str]() or list[Any]().

@Avasam
Copy link
Author

Avasam commented Nov 29, 2022

list[expected_subtype]() works great (python 3.9+) and it doesn't rely on casting! That syntax never crossed my mind, I'll have to remember that one. Thanks.
(in my actual use case the generic wasn't used for a return type, but with another optional parameter that I don't use, so I can really put w/e as the subtype)

@erictraut
Copy link
Collaborator

This is addressed in pyright 1.1.282, which I just published. It will also be included in a future release of pylance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants