ruff/crates/ty_python_semantic/resources/mdtest
David Peter c15aa572ff
[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](a83597fa88/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](a83597fa88/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](2408c028f4/stubs/pywin32/win32/lib/win32con.pyi (L594)),
but
[`LoadCursor`](2408c028f4/stubs/pywin32/win32/win32api.pyi (L197))
expects a
[`PyResourceId`](2408c028f4/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](2408c028f4/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
2025-07-07 13:16:40 +02:00
..
annotations [ty] Fix descriptor lookups for most types that overlap with None (#19120) 2025-07-05 19:34:23 +01:00
assignment [ty] Homogeneous and mixed tuples (#18600) 2025-06-20 18:23:54 -04:00
binary [ty] Homogeneous and mixed tuples (#18600) 2025-06-20 18:23:54 -04:00
boolean Revert "[ty] Better control flow for boolean expressions that are inside if (#18010)" (#18150) 2025-05-17 08:27:32 -04:00
boundness_declaredness Update class literal display to use <class 'Foo'> style (#17889) 2025-05-06 20:11:25 -04:00
call [ty] Fix descriptor lookups for most types that overlap with None (#19120) 2025-07-05 19:34:23 +01:00
class Update class literal display to use <class 'Foo'> style (#17889) 2025-05-06 20:11:25 -04:00
comparison [ty] Fix binary intersection comparison inference logic (#18266) 2025-05-23 12:55:17 +02:00
comprehensions Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
conditional [ty] Improve disjointness inference for NominalInstanceTypes and SubclassOfTypes (#18864) 2025-06-24 20:27:37 +00:00
dataclasses [ty] Restructure/move dataclass tests (#19117) 2025-07-03 10:36:14 +00:00
declaration [ty] Format conflicting types as an enumeration (#18956) 2025-06-26 14:29:33 +02:00
diagnostics [ty] Surface matched overload diagnostic directly (#18452) 2025-06-20 08:36:49 +05:30
directives [ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333) 2025-07-04 11:52:52 -07:00
doc ty_python_semantic: add union type context to function call type errors 2025-05-09 13:40:51 -04:00
exception [ty] support del statement and deletion of except handler names (#18593) 2025-06-12 07:44:42 -07:00
expression [ty] Make tuple instantiations sound (#18987) 2025-06-27 19:37:16 +01:00
function [ty] Add diagnosis for function with no return statement but with return type annotation (#18359) 2025-05-29 23:17:18 +00:00
generics [ty] Improve protocol member type checking and relation handling (#18847) 2025-06-29 10:46:33 +00:00
ide_support [ty] Minor tweaks to "list all members" docs and tests (#18388) 2025-05-30 13:36:57 -04:00
import [ty] don't allow first-party code to shadow stdlib types module (#19128) 2025-07-04 10:36:26 +00:00
literal [ty] Understand classes that inherit from subscripted Protocol[] as generic (#17832) 2025-05-09 17:39:15 +01:00
loops Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
narrow [ty] Eagerly simplify 'True' and 'False' constraints (#18998) 2025-06-30 13:11:52 +02:00
regression Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
scopes [ty] Infer nonlocal types as unions of all reachable bindings (#18750) 2025-06-26 12:24:40 +02:00
shadowing Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
snapshots [ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333) 2025-07-04 11:52:52 -07:00
stubs [ty] Do not carry the generic context of Protocol or Generic in the ClassBase enum (#17989) 2025-05-22 21:37:03 -04:00
subscript [ty] Fix false positives when subscripting an object inferred as having an Intersection type (#18920) 2025-06-24 18:39:02 +00:00
suppressions Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
type_compendium [ty] Make tuple instantiations sound (#18987) 2025-06-27 19:37:16 +01:00
type_of Update class literal display to use <class 'Foo'> style (#17889) 2025-05-06 20:11:25 -04:00
type_properties [ty] Add subtyping between SubclassOf and CallableType (#19026) 2025-07-02 19:22:31 -07:00
type_qualifiers [ty] Use RHS inferred type for bare Final symbols (#19142) 2025-07-07 13:16:40 +02:00
unary Update class literal display to use <class 'Foo'> style (#17889) 2025-05-06 20:11:25 -04:00
with [ty] Add hint if async context manager is used in non-async with statement (#18299) 2025-05-26 21:34:47 +02:00
.mdformat.toml Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
attributes.md [ty] Support declaration-only attributes (#19048) 2025-07-07 12:55:32 +02:00
cycle.md [ty] Add cycle handling for unpacking targets (#18078) 2025-05-13 21:27:48 +00:00
decorators.md ty_python_semantic: add union type context to function call type errors 2025-05-09 13:40:51 -04:00
del.md [ty] support del statement and deletion of except handler names (#18593) 2025-06-12 07:44:42 -07:00
descriptor_protocol.md [ty] Fix descriptor lookups for most types that overlap with None (#19120) 2025-07-05 19:34:23 +01:00
final.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
instance_layout_conflict.md [ty] Improve disjointness inference for NominalInstanceTypes and SubclassOfTypes (#18864) 2025-06-24 20:27:37 +00:00
intersection_types.md [ty] eliminate is_fully_static (#18799) 2025-06-24 18:02:05 -07:00
invalid_syntax.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
known_constants.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
mdtest_config.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
mdtest_custom_typeshed.md [ty] Fix duplicate diagnostics for unresolved module when an import from statement imports multiple members (#17886) 2025-05-06 12:37:10 +01:00
metaclass.md Update class literal display to use <class 'Foo'> style (#17889) 2025-05-06 20:11:25 -04:00
mro.md [ty] Implement implicit inheritance from Generic[] for PEP-695 generic classes (#18283) 2025-05-26 20:40:16 +01:00
named_tuple.md [ty] Add generic inference for dataclasses (#18443) 2025-06-03 09:59:43 -07:00
overloads.md [ty] Add support for @staticmethods (#18809) 2025-06-20 10:38:17 +02:00
pep695_type_aliases.md [ty] Support typing.TypeAliasType (#18156) 2025-05-19 16:36:49 +02:00
properties.md ty_python_semantic: add union type context to function call type errors 2025-05-09 13:40:51 -04:00
protocols.md [ty] detect cycles in Type::is_disjoint_from (#19139) 2025-07-04 06:31:44 -07:00
public_types.md [ty] Infer nonlocal types as unions of all reachable bindings (#18750) 2025-06-26 12:24:40 +02:00
statically_known_branches.md [ty] Infer nonlocal types as unions of all reachable bindings (#18750) 2025-06-26 12:24:40 +02:00
sys_platform.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
sys_version_info.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
terminal_statements.md [ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333) 2025-07-04 11:52:52 -07:00
type_api.md [ty] eliminate is_fully_static (#18799) 2025-06-24 18:02:05 -07:00
typed_dict.md Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
union_types.md [ty] eliminate is_fully_static (#18799) 2025-06-24 18:02:05 -07:00
unpacking.md [ty] Support variable-length tuples in unpacking assignments (#18948) 2025-06-27 15:29:04 -04:00
unreachable.md [ty] Infer nonlocal types as unions of all reachable bindings (#18750) 2025-06-26 12:24:40 +02:00