-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Narrow types based on collection containment #20602
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1373,13 +1373,13 @@ else: | |
| reveal_type(val) # N: Revealed type is "None" | ||
|
|
||
| if val in (None,): | ||
| reveal_type(val) # N: Revealed type is "__main__.A | None" | ||
| reveal_type(val) # N: Revealed type is "None" | ||
| else: | ||
| reveal_type(val) # N: Revealed type is "__main__.A | None" | ||
| reveal_type(val) # N: Revealed type is "__main__.A" | ||
| if val not in (None,): | ||
| reveal_type(val) # N: Revealed type is "__main__.A | None" | ||
| reveal_type(val) # N: Revealed type is "__main__.A" | ||
| else: | ||
| reveal_type(val) # N: Revealed type is "__main__.A | None" | ||
| reveal_type(val) # N: Revealed type is "None" | ||
|
|
||
| class Hmm: | ||
| def __eq__(self, other) -> bool: ... | ||
|
|
@@ -2294,9 +2294,8 @@ def f(x: str | int) -> None: | |
| y = x | ||
|
|
||
| if x in ["x"]: | ||
| # TODO: we should fix this reveal https://github.com/python/mypy/issues/3229 | ||
| reveal_type(x) # N: Revealed type is "builtins.str | builtins.int" | ||
| y = x # E: Incompatible types in assignment (expression has type "str | int", variable has type "str") | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| y = x | ||
| z = x | ||
| z = y | ||
| [builtins fixtures/primitives.pyi] | ||
|
|
@@ -2806,3 +2805,126 @@ class X: | |
| reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]" | ||
| self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist" | ||
| [builtins fixtures/dict.pyi] | ||
|
|
||
|
|
||
| [case testTypeNarrowingStringInLiteralUnion] | ||
| from typing import Literal, Tuple | ||
| typ: Tuple[Literal['a', 'b'], ...] = ('a', 'b') | ||
| x: str = "hi!" | ||
| if x in typ: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| [builtins fixtures/tuple.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testTypeNarrowingStringInLiteralUnionSubset] | ||
| from typing import Literal, Tuple | ||
| typeAlpha: Tuple[Literal['a', 'b', 'c'], ...] = ('a', 'b') | ||
| strIn: str = "b" | ||
| strOut: str = "c" | ||
| if strIn in typeAlpha: | ||
| reveal_type(strIn) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" | ||
| else: | ||
| reveal_type(strIn) # N: Revealed type is "builtins.str" | ||
| if strOut in typeAlpha: | ||
| reveal_type(strOut) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" | ||
| else: | ||
| reveal_type(strOut) # N: Revealed type is "builtins.str" | ||
| [builtins fixtures/primitives.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testNarrowingStringNotInLiteralUnion] | ||
| from typing import Literal, Tuple | ||
| typeAlpha: Tuple[Literal['a', 'b', 'c'],...] = ('a', 'b', 'c') | ||
| strIn: str = "c" | ||
| strOut: str = "d" | ||
| if strIn not in typeAlpha: | ||
| reveal_type(strIn) # N: Revealed type is "builtins.str" | ||
| else: | ||
| reveal_type(strIn) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" | ||
| if strOut in typeAlpha: | ||
| reveal_type(strOut) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" | ||
| else: | ||
| reveal_type(strOut) # N: Revealed type is "builtins.str" | ||
| [builtins fixtures/primitives.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testNarrowingStringInLiteralUnionDontExpand] | ||
| from typing import Literal, Tuple | ||
| typeAlpha: Tuple[Literal['a', 'b', 'c'], ...] = ('a', 'b', 'c') | ||
| strIn: Literal['c'] = "c" | ||
| reveal_type(strIn) # N: Revealed type is "Literal['c']" | ||
| #Check we don't expand a Literal into the Union type | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style nit: missing space after |
||
| if strIn not in typeAlpha: | ||
| reveal_type(strIn) # N: Revealed type is "Literal['c']" | ||
| else: | ||
| reveal_type(strIn) # N: Revealed type is "Literal['c']" | ||
| [builtins fixtures/primitives.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testTypeNarrowingStringInMixedUnion] | ||
| from typing import Literal, Tuple | ||
| typ: Tuple[Literal['a', 'b'], ...] = ('a', 'b') | ||
| x: str = "hi!" | ||
| if x in typ: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a duplicate test case?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I'd copied these tests from the first PR that attempted to make this change, but now I've just rewritten them completely from scratch |
||
| [builtins fixtures/tuple.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testTypeNarrowingStringInSet] | ||
| from typing import Literal, Set | ||
| typ: Set[Literal['a', 'b']] = {'a', 'b'} | ||
| x: str = "hi!" | ||
| if x in typ: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| if x not in typ: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| [builtins fixtures/narrowing.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testTypeNarrowingStringInList] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test type narrowing with an unsupported collection type ( Can this be used for enum type narrowing, e.g. if there is a union with enum type and non-enum type?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'd added a test for the consistency bug as a regression test the last time round. That is The implementation in this PR kind of guarantees that the narrowing we get from Yes, can be used for enum type narrowing when non-enum type is not ambiguous. I will add a test in a future PR (a later commit in my series rewrites some of those tests) |
||
| from typing import Literal, List | ||
| typ: List[Literal['a', 'b']] = ['a', 'b'] | ||
| x: str = "hi!" | ||
| if x in typ: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| if x not in typ: | ||
| reveal_type(x) # N: Revealed type is "builtins.str" | ||
| else: | ||
| reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" | ||
| [builtins fixtures/narrowing.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testTypeNarrowingUnionStringFloat] | ||
| from typing import Union | ||
| def foobar(foo: Union[str, float]): | ||
| if foo in ['a', 'b']: | ||
| reveal_type(foo) # N: Revealed type is "builtins.str" | ||
| else: | ||
| reveal_type(foo) # N: Revealed type is "builtins.str | builtins.float" | ||
| [builtins fixtures/primitives.pyi] | ||
| [typing fixtures/typing-medium.pyi] | ||
|
|
||
| [case testNarrowAnyWithEqualityOrContainment] | ||
| # https://github.com/python/mypy/issues/17841 | ||
| from typing import Any | ||
|
|
||
| def f1(x: Any) -> None: | ||
| if x is not None and x not in ["x"]: | ||
| return | ||
| reveal_type(x) # N: Revealed type is "Any" | ||
|
|
||
| def f2(x: Any) -> None: | ||
| if x is not None and x != "x": | ||
| return | ||
| reveal_type(x) # N: Revealed type is "Any" | ||
| [builtins fixtures/tuple.pyi] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is actually not really needed anymore. I will clean it up, there is one test case affected, a later PR in my stack changes it