ruff
c15aa572 - [ty] Use RHS inferred type for bare `Final` symbols (#19142)

Commit
70 days ago
[ty] Use RHS inferred type for bare `Final` symbols (#19142) ## Summary Infer the type of symbols with a `Final` qualifier as their right-hand-side inferred type: ```py x: Final = 1 y: Final[int] = 1 def _(): reveal_type(x) # previously: Unknown, now: Literal[1] reveal_type(y) # int, same as before ``` Part of https://github.com/astral-sh/ty/issues/158 ## Ecosystem analysis ### aiohttp ```diff aiohttp (https://github.com/aio-libs/aiohttp) + error[invalid-argument-type] aiohttp/compression_utils.py:131:54: Argument to bound method `__init__` is incorrect: Expected `ZLibBackendProtocol`, found `<module 'zlib'>` ``` This code [creates a protocol](https://github.com/aio-libs/aiohttp/blob/a83597fa88be7ac7dd5f6081d236d751cb40fe4d/aiohttp/compression_utils.py#L52-L77) that looks like ```pyi class ZLibBackendProtocol(Protocol): Z_FULL_FLUSH: int Z_SYNC_FLUSH: int # more fields… ``` It then [tries to assign](https://github.com/aio-libs/aiohttp/blob/a83597fa88be7ac7dd5f6081d236d751cb40fe4d/aiohttp/compression_utils.py#L131) the module literal `zlib` to that protocol. Howefer, in typeshed, these `zlib` members are annotated like this: ```pyi Z_FULL_FLUSH: Final = 3 Z_SYNC_FLUSH: Final = 2 ``` With the proposed change here, we now infer these as `Literal[3]` / `Literal[2]`. Since protocol members have to be assignable both ways (invariance), we do not consider `zlib` assignable to this protocol anymore. That seems rather unfortunate. Not sure who is to blame here? That `ZLibBackendProtocol` protocol should probably not annotate the members with `int`, given that `typeshed` doesn't use an explicit annotation here either? But what should they do instead? Annotate those fields with `Any`? Or is it another case where we should consider literal-widening? FYI @AlexWaygood ### cloud-init ```diff cloud-init (https://github.com/canonical/cloud-init) + error[invalid-argument-type] tests/unittests/sources/test_smartos.py:575:32: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float` + error[invalid-argument-type] tests/unittests/sources/test_smartos.py:593:32: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float` + error[invalid-argument-type] tests/unittests/sources/test_smartos.py:647:35: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float` ``` New false positives on expressions like `oct(os.stat(legacy_script_f)[stat.ST_MODE])`. We now correctly infer `stat.ST_MODE` as `Literal[1]`, because in typeshed, it is annotated as `ST_MODE: Final = 0`. `os.stat` returns a `stat_result` which is a tuple subclass. Accessing it at index 0 should return an `int`, but we currently return `int | float`, presumably due to missing support for tuple subclasses (FYI @AlexWaygood): ```pyi class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, float, float, float]): ``` In terms of `typing.Final`, things are working as expected here. ### pywin-32 Many new false positives similar to: ```diff pywin32 (https://github.com/mhammond/pywin32) + error[invalid-argument-type] Pythonwin/pywin/docking/DockingBar.py:288:55: Argument to function `LoadCursor` is incorrect: Expected `PyResourceId`, found `Literal[32645]` ``` The line in question calls `win32api.LoadCursor(0, win32con.IDC_ARROW)`. The `win32con.IDC_ARROW` symbol is annotated as [`IDC_ARROW: Final = 32512` in typeshed](https://github.com/python/typeshed/blob/2408c028f4f677e0db4eabef0c70e9f6ab33e365/stubs/pywin32/win32/lib/win32con.pyi#L594), but [`LoadCursor`](https://github.com/python/typeshed/blob/2408c028f4f677e0db4eabef0c70e9f6ab33e365/stubs/pywin32/win32/win32api.pyi#L197) expects a [`PyResourceId`](https://github.com/python/typeshed/blob/2408c028f4f677e0db4eabef0c70e9f6ab33e365/stubs/pywin32/_win32typing.pyi#L1252), which is an empty class. So.. this seems like a true positive to me, unless that typeshed annotation of `IDC_ARROW` is meant to imply that the type should be `Unknown`/`Any`? ### streamlit ```diff streamlit (https://github.com/streamlit/streamlit) + error[invalid-argument-type] lib/streamlit/string_util.py:163:37: Argument to bound method `translate` is incorrect: Expected `bytes`, found `bytearray` ``` This looks like a true positive? The code calls `inp.translate(None, TEXTCHARS)`. `inp` is `bytes`, and `TEXTCHARS` is: ```py TEXTCHARS: Final = bytearray( {7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F} ) ``` ~~We now infer this as `bytearray`, but `bytes.translate` [expects `bytes` for its `delete` parameter](https://github.com/python/typeshed/blob/2408c028f4f677e0db4eabef0c70e9f6ab33e365/stdlib/builtins.pyi#L710). This seems to work at runtime, so maybe the typeshed annotation is wrong?~~ (Edit: this is now fixed in typeshed) ```pycon >>> b"abc".translate(None, bytearray(b"b")) b'ac' ``` ## rotki ```diff + error[invalid-return-type] rotkehlchen/chain/ethereum/modules/yearn/decoder.py:412:13: Return type does not match returned value: expected `dict[Unknown, str]`, found `dict[Unknown, Literal["yearn-v1", "yearn-v2"]]` ``` The code in question looks like ```py def addresses_to_counterparties(self) -> dict[ChecksumEvmAddress, str]: return dict.fromkeys(self.vaults, CPT_BEEFY_FINANCE) ``` where `CPT_BEEFY_FINANCE: Final = 'beefy_finance'. We previously inferred the value type of the returned `dict` as `Unknown`, and now we infer it as `Literal["beefy_finance"]`, which does not match the annotated return type because `dict` is invariant in the value type. ```diff + error[invalid-argument-type] rotkehlchen/tests/unit/decoders/test_curve.py:249:9: Argument is incorrect: Expected `int`, found `FVal` ``` There are true positives that were previously silenced through the `Unknown`. ## Test Plan New Markdown tests
Author
Parents
Loading