-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[ty] Use RHS inferred type for bare Final
symbols
#19142
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
Conversation
|
||
`mod.py`: | ||
|
||
```py | ||
from typing import Final, Annotated | ||
|
||
FINAL_A: int = 1 |
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.
Pretty sure I intended to write FINAL_A: Final[int] = 1
here originally :-)
|
I think our behaviour is correct here. Even if we put aside the issue of variance, the module still shouldn't satisfy For the module to satisfy the protocol, either the members on the module would need to be made mutable (and declared as - Z_FULL_FLUSH: Final = 3
- Z_SYNC_FLUSH: Final = 2
+ Z_FULL_FLUSH: int = 3
+ Z_SYNC_FLUSH: int = 2 or the protocol would need to be changed so that it uses immutable, covariant members rather than mutable, invariant members. The only way to do that on a protocol currently (at least until https://peps.python.org/pep-0767/ is approved) is to use class ZLibBackendProtocol(Protocol):
- Z_FULL_FLUSH: int
- Z_SYNC_FLUSH: int
+ @property
+ def Z_FULL_FLUSH(self) -> int: ...
+ @property
+ def Z_SYNC_FLUSH(self) -> int: ... |
yeah, that does sound like a true positive. Possibly a typeshed bug? |
This comment was marked as resolved.
This comment was marked as resolved.
7d591b2
to
9e1657d
Compare
Final
symbolsFinal
symbols
9e1657d
to
bf1ddb2
Compare
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.
nice!
/// Returns `Some(…)` if the place is qualified with `typing.Final` without a specified type. | ||
pub(crate) fn is_bare_final(&self) -> Option<TypeQualifiers> { | ||
match self { | ||
PlaceAndQualifiers { place, qualifiers } | ||
if (qualifiers.contains(TypeQualifiers::FINAL) | ||
&& place | ||
.ignore_possibly_unbound() | ||
.is_some_and(|ty| ty.is_unknown())) => | ||
{ | ||
Some(*qualifiers) | ||
} | ||
_ => None, | ||
} | ||
} |
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.
Does this mean that we would consider x
to be "qualified with bare Final
" here?
from ty_extensions import Unknown
from typing import Final
x: Final[Unknown]
I think that's okay if so; people just shouldn't do that ;)
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.
Yes, well spotted 😄. I agree that it's not as clean as it could be. The alternative is much more complicated as it requires us to return Option<Type>
and TypeQualifiers
instead of just Type
and TypeQualifiers
from infer_annotation_expression
and related methods (such that we could return None
for a bare Final
or ClassVar
), which has a lot of downstream consequences.
…c_tokens * 'main' of https://github.com/astral-sh/ruff: (27 commits) [ty] First cut at semantic token provider (astral-sh#19108) [`flake8-simplify`] Make example error out-of-the-box (`SIM116`) (astral-sh#19111) [`flake8-use-pathlib`] Make example error out-of-the-box (`PTH210`) (astral-sh#19189) [`flake8-use-pathlib`] Add autofixes for `PTH203`, `PTH204`, `PTH205` (astral-sh#18922) [`flake8-type-checking`] Fix syntax error introduced by fix (`TC008`) (astral-sh#19150) [`flake8-pyi`] Make example error out-of-the-box (`PYI007`, `PYI008`) (astral-sh#19103) Update Rust crate indicatif to 0.18.0 (astral-sh#19165) [ty] Add separate CI job for memory usage stats (astral-sh#19134) [ty] Add documentation for server traits (astral-sh#19137) Rename to `SessionSnapshot`, move unwind assertion closer (astral-sh#19177) [`flake8-type-checking`] Make example error out-of-the-box (`TC001`) (astral-sh#19151) [ty] Bare `ClassVar` annotations (astral-sh#15768) [ty] Re-enable multithreaded pydantic benchmark (astral-sh#19176) [ty] Implement equivalence for protocols with method members (astral-sh#18659) [ty] Use RHS inferred type for bare `Final` symbols (astral-sh#19142) [ty] Support declaration-only attributes (astral-sh#19048) [ty] Sync vendored typeshed stubs (astral-sh#19174) Update dependency pyodide to ^0.28.0 (astral-sh#19164) Update NPM Development dependencies (astral-sh#19170) Update taiki-e/install-action action to v2.56.7 (astral-sh#19169) ...
Summary
Infer the type of symbols with a
Final
qualifier as their right-hand-side inferred type:Part of astral-sh/ty#158
Ecosystem analysis
aiohttp
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 that looks like
It then tries to assign the module literal
zlib
to that protocol. Howefer, in typeshed, thesezlib
members are annotated like this: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 considerzlib
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 withint
, given thattypeshed
doesn't use an explicit annotation here either? But what should they do instead? Annotate those fields withAny
?Or is it another case where we should consider literal-widening?
FYI @AlexWaygood
cloud-init
New false positives on expressions like
oct(os.stat(legacy_script_f)[stat.ST_MODE])
. We now correctly inferstat.ST_MODE
asLiteral[1]
, because in typeshed, it is annotated asST_MODE: Final = 0
.os.stat
returns astat_result
which is a tuple subclass. Accessing it at index 0 should return anint
, but we currently returnint | float
, presumably due to missing support for tuple subclasses (FYI @AlexWaygood):In terms of
typing.Final
, things are working as expected here.pywin-32
Many new false positives similar to:
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)
. Thewin32con.IDC_ARROW
symbol is annotated asIDC_ARROW: Final = 32512
in typeshed, butLoadCursor
expects aPyResourceId
, which is an empty class. So.. this seems like a true positive to me, unless that typeshed annotation ofIDC_ARROW
is meant to imply that the type should beUnknown
/Any
?streamlit
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
isbytes
, andTEXTCHARS
is:We now infer this as(Edit: this is now fixed in typeshed)bytearray
, butbytes.translate
expectsbytes
for itsdelete
parameter. This seems to work at runtime, so maybe the typeshed annotation is wrong?rotki
+ 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
where
CPT_BEEFY_FINANCE: Final = 'beefy_finance'. We previously inferred the value type of the returned
dictas
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.+ 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