diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 66e7c997f4fad..b8303342a254c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,7 +44,7 @@ jobs: with: python-version: '3.12' - name: Install tox - run: pip install tox==4.26.0 + run: pip install tox==4.53.1 - name: Setup tox environment run: tox run -e ${{ env.TOXENV }} --notest - name: Test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5242739f8f846..b06f181746c35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,12 @@ jobs: toxenv: py tox_extra_args: "-n 4" test_mypyc: true + - name: Test suite with py315-ubuntu, mypyc-compiled + python: '3.15' + os: ubuntu-24.04-arm + toxenv: py + tox_extra_args: "-n 4" + test_mypyc: true - name: Test suite with py314t-ubuntu, mypyc-compiled python: '3.14t' os: ubuntu-24.04-arm @@ -196,6 +202,7 @@ jobs: if: ${{ !(matrix.debug_build || endsWith(matrix.python, '-dev')) }} with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Install tox run: | @@ -206,7 +213,7 @@ jobs: echo debug build; python -c 'import sysconfig; print(bool(sysconfig.get_config_var("Py_DEBUG")))' echo os.cpu_count; python -c 'import os; print(os.cpu_count())' echo os.sched_getaffinity; python -c 'import os; print(len(getattr(os, "sched_getaffinity", lambda *args: [])(0)))' - pip install tox==4.26.0 + pip install tox==4.53.1 - name: Compiled with mypyc if: ${{ matrix.test_mypyc }} @@ -271,7 +278,7 @@ jobs: default: 3.11.1 command: python -c "import platform; print(f'{platform.architecture()=} {platform.machine()=}');" - name: Install tox - run: pip install tox==4.26.0 + run: pip install tox==4.53.1 - name: Setup tox environment run: tox run -e py --notest - name: Test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 361290e55a1d1..548b568621a70 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=HAX,Nam,ccompiler,ot,statics,whet,zar + - --ignore-words-list=HAX,Nam,ccompiler,keep-alives,ot,statics,whet,zar exclude: ^(mypy/test/|mypy/typeshed/|mypyc/test-data/|test-data/).+$ - repo: https://github.com/rhysd/actionlint rev: v1.7.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d4f487a030a8..efe63effbfb4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,479 @@ ## Next Release +## Mypy 2.3 + +We've just uploaded mypy 2.3.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +Mypy is a static type checker for Python. This release includes new features, performance +improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +### The Upcoming Switch to the New Native Parser + +We are planning to enable the new native parser (`--native-parser`) by +default soon. We recommend that you test the native parser in your projects and report +any issues in the [mypy issue tracker](https://github.com/python/mypy/issues). + +### Mypyc Free-threading Memory Safety + +Free-threaded Python builds that don't have the GIL require additional synchronization +primitives or lock-free algorithms to ensure memory safety when there are race conditions +(for example, when a thread reads a list item while another thread writes the same list +item concurrently). This release greatly improves memory safety of free threading. + +List operations are now memory-safe on free threaded Python builds, even in the presence of +race conditions. This has some performance cost. For list-heavy workloads, using +`librt.vecs.vec` instead of list is often significantly faster, but note that `vec` is not +(and likely won't be) fully memory safe, and the user is expected to avoid race conditions. +The newly introduced `librt.threading.Lock` helps with this. Using variable-length tuples +can also be more efficient than lists, since tuples are immutable and don't require +expensive synchronization to ensure memory safety. + +Instance attribute access is also (mostly) memory safe now on free-threaded builds in +the presence of race conditions. We are planning to fix the remaining unsafe cases in a +future release. + +Full list of changes: + +- Make attribute access memory safe on free-threaded builds (Jukka Lehtosalo, PR [21705](https://github.com/python/mypy/pull/21705)) +- Fix unsafe borrowing of instance attributes with free-threading (Jukka Lehtosalo, PR [21688](https://github.com/python/mypy/pull/21688)) +- Make list get/set item more memory safe on free-threaded builds (Jukka Lehtosalo, PR [21683](https://github.com/python/mypy/pull/21683)) +- Don't borrow list items on free-threaded builds (Jukka Lehtosalo, PR [21679](https://github.com/python/mypy/pull/21679)) +- Make multiple assignment from list memory-safe on free-threaded builds (Jukka Lehtosalo, PR [21684](https://github.com/python/mypy/pull/21684)) +- Make `for` loop over list memory-safe on free-threaded builds (Jukka Lehtosalo, PR [21686](https://github.com/python/mypy/pull/21686)) +- Fix memory safety of `list.count` on free-threaded builds (Jukka Lehtosalo, PR [21680](https://github.com/python/mypy/pull/21680)) +- Make `vec` creation from list memory safe on free-threaded builds (Jukka Lehtosalo, PR [21681](https://github.com/python/mypy/pull/21681)) + +### librt.threading: Fast Native Lock Type + +Mypyc now supports `librt.threading.Lock`, which is a lock type optimized for use +in compiled code. It can be 2x to 4x faster than `threading.Lock`. + +This feature was contributed by Jukka Lehtosalo (PR [21690](https://github.com/python/mypy/pull/21690), PR [21697](https://github.com/python/mypy/pull/21697)). + +### Mypyc: Read-only Final Instance Attributes + +Instance attributes of native classes declared as `Final` are now read-only at runtime. +This enables additional optimizations, and it's now recommended to use `Final` for +all performance-sensitive attributes when feasible. + +Related changes: + +- Make instance attribute read-only at runtime if `Final` (Jukka Lehtosalo, PR [21666](https://github.com/python/mypy/pull/21666)) +- Borrow final attributes more aggressively (Jukka Lehtosalo, PR [21702](https://github.com/python/mypy/pull/21702)) +- Improve documentation of `Final` in mypyc (Jukka Lehtosalo, PR [21713](https://github.com/python/mypy/pull/21713)) + +### Mypyc Documentation Updates + +- Update documentation of race conditions under free threading (Jukka Lehtosalo, PR [21726](https://github.com/python/mypy/pull/21726)) +- Update mypyc free threading Python compatibility docs (Jukka Lehtosalo, PR [21711](https://github.com/python/mypy/pull/21711)) +- Document recent additions to `librt.strings`, such as `ispace` (Jukka Lehtosalo, PR [21696](https://github.com/python/mypy/pull/21696)) + +### Miscellaneous Mypyc Improvements + +- Fix reference leak when setting unboxed refcounted attributes (Tom Bannink, PR [21657](https://github.com/python/mypy/pull/21657)) +- Fix function wrapper memory leak (Piotr Sawicki, PR [21654](https://github.com/python/mypy/pull/21654)) +- Fix handling of invalid codepoint values in `librt.strings` (Jukka Lehtosalo, PR [21634](https://github.com/python/mypy/pull/21634)) +- Fix non-deterministic ordering of spilled registers (Jukka Lehtosalo, PR [21632](https://github.com/python/mypy/pull/21632)) +- Fix non-deterministic compiler output due to frozensets (Jukka Lehtosalo, PR [21631](https://github.com/python/mypy/pull/21631)) + +### Changes to Messages + +- Fix error code of note about unbound type variable (Jukka Lehtosalo, PR [21668](https://github.com/python/mypy/pull/21668)) + +### Other Notable Fixes and Improvements + +- Use `PYODIDE` environment variable for Emscripten cross-compilation detection (Agriya Khetarpal, PR [21714](https://github.com/python/mypy/pull/21714)) +- Narrow for frozendict membership check (Shantanu, PR [21709](https://github.com/python/mypy/pull/21709)) +- Fix custom equality handling for membership narrowing in static containers (Shantanu, PR [21706](https://github.com/python/mypy/pull/21706)) +- Infer `Coroutine` for unannotated async functions (Jingchen Ye, PR [21651](https://github.com/python/mypy/pull/21651)) +- Fix variance inference issues caused by dataclass replace (Shantanu, PR [21694](https://github.com/python/mypy/pull/21694)) +- Fix regression in dataclass narrowing for Python >= 3.13 (ygale, PR [21675](https://github.com/python/mypy/pull/21675)) +- Fix star import dependencies in mypy daemon (Jukka Lehtosalo, PR [21673](https://github.com/python/mypy/pull/21673)) +- Fix skipped imports considered stale (Piotr Sawicki, PR [21639](https://github.com/python/mypy/pull/21639)) +- Support `.ff` files with `--cache-map` (Jukka Lehtosalo, PR [21633](https://github.com/python/mypy/pull/21633)) + +### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=f76037a1eb3923c67a8bc0e302ee9c016ffb3431+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +- Agriya Khetarpal +- Ethan Sarp +- Ivan Levkivskyi +- Jingchen Ye +- Jukka Lehtosalo +- Piotr Sawicki +- Shantanu +- Tom Bannink +- Viktor Szépe +- ygale + +I'd also like to thank my employer, Dropbox, for supporting mypy development. + +## Mypy 2.2 + +We've just uploaded mypy 2.2.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +Mypy is a static type checker for Python. This release includes new features, performance +improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +### Support for Closed TypedDicts (PEP 728) + +Mypy now supports closed TypedDicts as specified in PEP 728. A closed TypedDict cannot have extra +keys beyond those explicitly defined. This allows the type checker to determine that certain +operations are safe when they otherwise wouldn't be due to the potential presence of unknown keys. + +You can use the `closed` keyword argument with `TypedDict`: + +```python +HasName = TypedDict("HasName", {"name": str}) +HasOnlyName = TypedDict("HasOnlyName", {"name": str}, closed=True) +Movie = TypedDict("Movie", {"name": str, "year": int}) + +movie: Movie = {"name": "Nimona", "year": 2023} +has_name: HasName = movie # OK: HasName is open (default) +has_only_name: HasOnlyName = movie # Error: HasOnlyName is closed and Movie has extra "year" key +``` + +Closed TypedDicts enable more precise type checking because the type checker knows exactly which +keys are present. This is particularly useful when working with TypedDict unions or when you want +to ensure that a TypedDict conforms to an exact shape. + +The `closed` keyword also enables safe type narrowing with `in` checks: + +```python +Book = TypedDict('Book', {'book': str}, closed=True) +DVD = TypedDict('DVD', {'dvd': str}, closed=True) +type Inventory = Book | DVD + +def print_type(inventory: Inventory) -> None: + if "book" in inventory: + # Type is narrowed to Book here - safe because DVD is closed + print(inventory["book"]) + else: + # Type is narrowed to DVD here + print(inventory["dvd"]) +``` + +The `closed` keyword is also supported in class-based syntax: + +```python +class HasOnlyName(TypedDict, closed=True): + name: str +``` + +Note that closed TypedDicts are structural types, so a closed TypedDict is assignable to an open +TypedDict with the same keys, but not vice versa. + +Contributed by Alice (PR [21382](https://github.com/python/mypy/pull/21382)). + +### Complete Support for Type Variable Defaults (PEP 696) + +Mypy now has complete support for type variable defaults as specified in PEP 696. This allows you to +specify default values for type parameters in generic classes, functions, and type aliases. + +Traditional syntax (Python 3.11 and earlier): + +```python +T = TypeVar("T", default=int) # This means that if no type is specified T = int + +@dataclass +class Box(Generic[T]): + value: T | None = None + +reveal_type(Box()) # type is Box[int] +reveal_type(Box(value="Hello World!")) # type is Box[str] +``` + +New syntax (Python 3.12+): + +```python +class Box[T = int]: + def __init__(self, value: T) -> None: + self.value = value + +reveal_type(Box()) # type is Box[int] +reveal_type(Box(value="Hello World!")) # type is Box[str] +``` + +Type variable defaults work with all forms of generics, including classes, functions, and type aliases. +This release completes the implementation by fixing various edge cases involving recursive defaults, +dependencies between type variables, and interactions with variadic generics. + +Contributed by Ivan Levkivskyi (PRs [21491](https://github.com/python/mypy/pull/21491), +[21526](https://github.com/python/mypy/pull/21526), [21544](https://github.com/python/mypy/pull/21544)). + +### Respect Explicit Return Type of `__new__()` + +Mypy now respects explicitly annotated return types in `__new__()` methods. Previously, mypy would +always assume that `__new__()` returns an instance of the current class, ignoring explicit annotations. + +With this change, if you explicitly annotate a return type that differs from the implicit type, mypy +will use the explicit annotation: + +```python +class Factory: + def __new__(cls) -> Product: + return Product() + +reveal_type(Factory()) # type is Product, not Factory +``` + +Note that mypy still gives an error at the definition site if the explicit annotation is not a +subtype of the current class, since this is technically not type-safe. + +For backwards compatibility, there are two exceptions: +- If the return type is `Any`, mypy will still use the current class as the return type. +- If the explicit return type comes from a superclass and is a supertype of the implicit return type, + mypy will use the implicit (more specific) type: + +```python +class A: + def __new__(cls) -> A: ... + +reveal_type(A()) # type is A + +class B: + def __new__(cls) -> B: + return cls() + +class C(B): ... +reveal_type(C()) # type is C +``` + +This fixes several long-standing issues where explicit `__new__()` return types were ignored. + +Contributed by Ivan Levkivskyi (PR [21441](https://github.com/python/mypy/pull/21441)). + +### TypeForm Support No Longer Experimental + +Support for `TypeForm` is no longer experimental. `TypeForm` (introduced in Python 3.14) allows you +to annotate parameters that accept type expressions, providing better type checking for functions +that work with types as values. + +```python +from typing import TypeForm + +def make_list(tp: TypeForm[T]) -> list[T]: + ... + +# Correctly typed as list[int] +int_list = make_list(int) +``` + +`TypeForm` support was previously reverted from mypy 2.1 due to a performance regression, but this +has now been mitigated. + +Contributed by Ivan Levkivskyi and Jelle Zijlstra (PRs [21262](https://github.com/python/mypy/pull/21262), [21591](https://github.com/python/mypy/pull/21591), +[21459](https://github.com/python/mypy/pull/21459)). + +### Experimental WASM Wheel for Python 3.14 + +Mypy now ships an experimental WebAssembly (WASM) wheel for Python 3.14. This allows mypy to run +in WASM environments such as Pyodide and browser-based Python implementations. + +The WASM wheel is considered experimental and may have limitations compared to native builds. Please +report any issues you encounter when using mypy in WASM environments. + +Contributed by Ivan Levkivskyi (PR [21671](https://github.com/python/mypy/pull/21671)). + +### Mypyc Free-threading Improvements + +- Make function wrappers thread-safe on free-threaded builds (Jukka Lehtosalo, PR [21620](https://github.com/python/mypy/pull/21620)) +- Make list remove and index thread-safe on free-threaded builds (Jukka Lehtosalo, PR [21614](https://github.com/python/mypy/pull/21614)) +- Fix dict iteration memory safety on free-threaded builds (Jukka Lehtosalo, PR [21617](https://github.com/python/mypy/pull/21617)) +- Make some dict primitives thread-safe on free-threading builds (Jukka Lehtosalo, PR [21616](https://github.com/python/mypy/pull/21616)) +- Fix free-threading race condition in argument parsing (Jukka Lehtosalo, PR [21613](https://github.com/python/mypy/pull/21613)) +- Document free threading and other doc updates (Jukka Lehtosalo, PR [21494](https://github.com/python/mypy/pull/21494)) + +### `librt.strings` Updates + +- Add `librt.strings.toupper` and `librt.strings.tolower` codepoint primitives (Vaggelis Danias, PR [21553](https://github.com/python/mypy/pull/21553)) +- Add `librt.strings.isidentifier` codepoint primitive (Vaggelis Danias, PR [21522](https://github.com/python/mypy/pull/21522)) +- Add `librt.strings.isalpha` codepoint primitive (Vaggelis Danias, PR [21521](https://github.com/python/mypy/pull/21521)) +- Add `librt.strings.isalnum` codepoint primitive (Vaggelis Danias, PR [21509](https://github.com/python/mypy/pull/21509)) +- Add `librt.strings.isdigit` codepoint primitive (Vaggelis Danias, PR [21504](https://github.com/python/mypy/pull/21504)) +- Add `librt.strings.isspace` char primitive (Vaggelis Danias, PR [21462](https://github.com/python/mypy/pull/21462)) + +### Mypyc Improvements + +- Fix name lookup when class var and module var have the same name (Jukka Lehtosalo, PR [21594](https://github.com/python/mypy/pull/21594)) +- Report file and line number on uncaught exceptions (Jukka Lehtosalo, PR [21584](https://github.com/python/mypy/pull/21584)) +- Use `other` arg instead of `self` for RHS type (Ryan Heard, PR [21569](https://github.com/python/mypy/pull/21569)) +- Use `method_sig` to get the method signature (Ryan Heard, PR [21567](https://github.com/python/mypy/pull/21567)) +- Preserve inherited attribute defaults under `separate=True` (Jo, PR [21547](https://github.com/python/mypy/pull/21547)) +- Fix missing cross-group header deps in incremental builds (Jo, PR [21490](https://github.com/python/mypy/pull/21490)) +- Fix cross-group call to inherited `__mypyc_defaults_setup` (Jo, PR [21481](https://github.com/python/mypy/pull/21481)) +- Fix non-deterministic class struct layout under `separate=True` (Vaggelis Danias, PR [21530](https://github.com/python/mypy/pull/21530)) +- Specialize `s[i] == 'x'` to a codepoint int compare (Vaggelis Danias, PR [21579](https://github.com/python/mypy/pull/21579)) +- Fix reference leak in mypyc bytes concatenation (Colinxu2020, PR [21469](https://github.com/python/mypy/pull/21469)) + +### Fixes to Crashes + +- Fix crash on invalid recursive variadic alias (Ivan Levkivskyi, PR [21572](https://github.com/python/mypy/pull/21572)) +- Fix crashes on variadic unpacking in synthetic types (Ivan Levkivskyi, PR [21555](https://github.com/python/mypy/pull/21555)) +- Fix crash on unhandled meet variadic tuple vs instance (Ivan Levkivskyi, PR [21558](https://github.com/python/mypy/pull/21558)) +- Fix crash on deferred generic class nested in function (Ivan Levkivskyi, PR [21557](https://github.com/python/mypy/pull/21557)) +- Fix crash in new-style type alias with variadic unpack (Ivan Levkivskyi, PR [21551](https://github.com/python/mypy/pull/21551)) +- Fix various crashes on recursive type variable defaults (Ivan Levkivskyi, PR [21491](https://github.com/python/mypy/pull/21491)) +- Fix crash for empty `Annotated` type application (Rayan Salhab, PR [21503](https://github.com/python/mypy/pull/21503)) +- Fix crash on `Unpack` used without arguments in class bases (Sai Asish Y, PR [21470](https://github.com/python/mypy/pull/21470)) + +### Performance Improvements + +- Memoize the options snapshot (Kevin Kannammalil, PR [21354](https://github.com/python/mypy/pull/21354)) +- Don't include `not_ready_deps` tracking as relating to mypy internals (Kevin Kannammalil, PR [21389](https://github.com/python/mypy/pull/21389)) +- Speed up transitive dependency hash for singleton SCCs (Kevin Kannammalil, PR [21390](https://github.com/python/mypy/pull/21390)) +- Optimize typeform checks (Jelle Zijlstra, PR [21459](https://github.com/python/mypy/pull/21459)) + +### Improvements to the Native Parser + +- Support `--shadow-file` with `--native-parser` (Jukka Lehtosalo, PR [21623](https://github.com/python/mypy/pull/21623)) +- Add Python version checks to native parser (Kevin Kannammalil, PR [21539](https://github.com/python/mypy/pull/21539)) +- Allow nativeparse to parse source code directly (bzoracler, PR [21260](https://github.com/python/mypy/pull/21260)) + +### Other Notable Fixes and Improvements + +- Add function definition notes for `too many positional arguments` errors (Kevin Kannammalil, PR [21410](https://github.com/python/mypy/pull/21410)) +- Fix the exportjson tool (.ff cache to .json conversion) (Jukka Lehtosalo, PR [21628](https://github.com/python/mypy/pull/21628)) +- Support floats in JSON in fixed-format cache (Ivan Levkivskyi, PR [21603](https://github.com/python/mypy/pull/21603)) +- Update `TypedDictType.__init__` signature to preserve backward compat (Jukka Lehtosalo, PR [21590](https://github.com/python/mypy/pull/21590)) +- Fix constructor calls for union-bounded `TypeVar`s (Jingchen Ye, PR [21571](https://github.com/python/mypy/pull/21571)) +- Fix `TypedDict` indexing with literal keys in comprehensions (Jingchen Ye, PR [21556](https://github.com/python/mypy/pull/21556)) +- Correctly handle empty tuple index when unpacked (Ivan Levkivskyi, PR [21545](https://github.com/python/mypy/pull/21545)) +- Support protocol checks for self-types in tuple types (Ivan Levkivskyi, PR [21535](https://github.com/python/mypy/pull/21535)) +- Fix edge cases in variadic tuple subclasses (Ivan Levkivskyi, PR [21518](https://github.com/python/mypy/pull/21518)) +- Special-case constructor for tuple types (Ivan Levkivskyi, PR [21502](https://github.com/python/mypy/pull/21502)) +- Fix false positive "Expected TypedDict key to be string literal" for `Union[TypedDict, dict[K, V]]` (Zakir Jiwani, PR [21511](https://github.com/python/mypy/pull/21511)) +- Use explicit `Never` for type inference (Ivan Levkivskyi, PR [21497](https://github.com/python/mypy/pull/21497)) +- Narrow membership in statically known containers (Shantanu, PR [21461](https://github.com/python/mypy/pull/21461)) +- Improve negative narrowing for membership checks on tuples (Shantanu, PR [21456](https://github.com/python/mypy/pull/21456)) +- Analyze `TypedDict` decorators (Pranav Manglik, PR [21267](https://github.com/python/mypy/pull/21267)) +- Start testing Python 3.15 (Marc Mueller, PR [21439](https://github.com/python/mypy/pull/21439)) +- Improved handling of `NamedTuple`, `TypedDict`, `Enum`, and regular classes nested in functions (Ivan Levkivskyi, PR [21478](https://github.com/python/mypy/pull/21478)) + +### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=616424285beccaa76f90e87e1e922b1dc68710ca+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +- Adam Turner +- alicederyn +- bzoracler +- Colinxu2020 +- georgesittas +- Ivan Levkivskyi +- Jelle Zijlstra +- Jingchen Ye +- Jukka Lehtosalo +- Kevin Kannammalil +- lphuc2250gma +- Marc Mueller +- Pranav Manglik +- Rayan Salhab +- Ryan Heard +- Sai Asish Y +- Shantanu +- sobolevn +- Vaggelis Danias +- Victor Letichevsky +- Zakir Jiwani + +I'd also like to thank my employer, Dropbox, for supporting mypy development. + +## Mypy 2.1 + +We’ve just uploaded mypy 2.1.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). +Mypy is a static type checker for Python. This release includes new features, performance +improvements and bug fixes. You can install it as follows: + + python3 -m pip install -U mypy + +You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io). + +### librt.vecs: Fast Growable Array Type for Mypyc + +The new `librt.vecs` module provides an efficient growable array type `vec` that is +optimized for mypyc use. It provides fast, packed arrays with integer and floating point +value types, which can be **several times faster** than `list`, and tens of times faster +than `array.array` in code compiled using mypyc. It also supports nested `vec` objects and +non-value-type items, such as ``vec[vec[str]]``. + +Refer to the [documentation](https://mypyc.readthedocs.io/en/latest/librt_vecs.html) for +the details. + +Contributed by Jukka Lehtosalo. + +### librt.random: Fast Pseudo-Random Number Generation + +The new `librt.random` module provides fast pseudo-random number generation that is +optimized for code compiled using mypyc. It can be 3x to 10x faster than the stdlib +`random` module in compiled code. + +Refer to the [documentation](https://mypyc.readthedocs.io/en/latest/librt_random.html) for +the details. + +Contributed by Jukka Lehtosalo (PR [21433](https://github.com/python/mypy/pull/21433)). + +### Mypyc Improvements + +- Enable incremental self-compilation (Vaggelis Danias, PR [21369](https://github.com/python/mypy/pull/21369)) +- Make compilation order with multiple files consistent (Piotr Sawicki, PR [21419](https://github.com/python/mypy/pull/21419)) +- Fix crash on accessing `StopAsyncIteration` (Piotr Sawicki, PR [21406](https://github.com/python/mypy/pull/21406)) +- Fix incremental compilation with `separate` flag (Vaggelis Danias, PR [21299](https://github.com/python/mypy/pull/21299)) + +### Fixes to Crashes + +- Fix crash on partial type with `--allow-redefinition` and `global` declaration (Jukka Lehtosalo, PR [21428](https://github.com/python/mypy/pull/21428)) +- Fix broken awaitable generator patching (Ivan Levkivskyi, PR [21435](https://github.com/python/mypy/pull/21435)) + +### Changes to Messages + +- Fix function call error message for small number of arguments (sobolevn, PR [21432](https://github.com/python/mypy/pull/21432)) + +### Other Notable Fixes and Improvements + +- Rely on typeshed stubs for `slice` typing (Ivan Levkivskyi, PR [21401](https://github.com/python/mypy/pull/21401)) +- Narrow match captures based on previous cases (Shantanu, PR [21405](https://github.com/python/mypy/pull/21405)) +- Fix nondeterminism in overload resolution (Shantanu, PR [21455](https://github.com/python/mypy/pull/21455)) +- Respect file config comments for stale modules (Adam Turner, PR [21444](https://github.com/python/mypy/pull/21444)) +- Fix JSON output mode for syntax errors in parallel mode (Adam Turner, PR [21434](https://github.com/python/mypy/pull/21434)) +- Fix type variable with values as a supertype (Ivan Levkivskyi, PR [21431](https://github.com/python/mypy/pull/21431)) +- Add support for configuring `--num-workers` with an environment variable (Kevin Kannammalil, PR [21407](https://github.com/python/mypy/pull/21407)) +- Respect JSON output mode for syntax errors (Adam Turner, PR [21386](https://github.com/python/mypy/pull/21386)) + +### Typeshed Updates + +Please see [git log](https://github.com/python/typeshed/commits/main?after=e4d32e01bee44241a5e7c33298c261175b9f1bdb+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes. + +### Acknowledgements + +Thanks to all mypy contributors who contributed to this release: + +- Adam Turner +- Ivan Levkivskyi +- Jukka Lehtosalo +- Kevin Kannammalil +- Piotr Sawicki +- Shantanu +- sobolevn +- Vaggelis Danias + +I’d also like to thank my employer, Dropbox, for supporting mypy development. + ## Mypy 2.0 We’ve just uploaded mypy 2.0.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). @@ -369,7 +842,7 @@ Contributed by Marc Mueller (PR [20156](https://github.com/python/mypy/pull/2015 For best performance, mypy can be compiled to C extension modules using mypyc. This makes mypy 3-5x faster than when interpreted with pure Python. We now build and upload mypyc accelerated mypy wheels for `win_arm64` and `cp314t-...` to PyPI, making it easy for Windows -users on ARM and those using the free theading builds for Python 3.14 to realise this speedup +users on ARM and those using the free threading builds for Python 3.14 to realise this speedup -- just `pip install` the latest mypy. Contributed by Marc Mueller diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28828482706da..f4c1dcc89c16f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,6 +123,18 @@ tox -e dev --override testenv:dev.allowlist_externals+=env -- env # inspect the If you don't already have `tox` installed, you can use a virtual environment as described above to install `tox` via `pip` (e.g., ``python -m pip install tox``). +## LLM-assisted contributions + +In general, mypy takes a neutral stance on using various LLM code assistants to +make contributions. LLMs are just another tool, and contributors bear full +responsibility for the code they are submitting. Disclosing the use of LLMs in +pull request description is recommended, but not required. + +However, we discourage use of LLMs by *new* contributors. We are interested in +growing long-term contributors who have good understanding of mypy code. +Pull requests from new contributors that are mostly generated by LLMs +with little human input will be closed. + ## First time contributors If you're looking for things to help with, browse our [issue tracker](https://github.com/python/mypy/issues)! diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst index 36cf05d4f3f85..5dfe483d2cfe2 100644 --- a/docs/source/error_code_list2.rst +++ b/docs/source/error_code_list2.rst @@ -550,7 +550,7 @@ Example: Check that overrides of mutable attributes are safe [mutable-override] ---------------------------------------------------------------------- -`mutable-override` will enable the check for unsafe overrides of mutable attributes. +``mutable-override`` will enable the check for unsafe overrides of mutable attributes. For historical reasons, and because this is a relatively common pattern in Python, this check is not enabled by default. The example below is unsafe, and will be flagged when this error code is enabled: diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 26c2f002231e6..b736078a796cf 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -443,7 +443,7 @@ case you should add an explicit ``... | None`` annotation. The Python interpreter internally uses the name ``NoneType`` for the type of ``None``, but ``None`` is always used in type annotations. The latter is shorter and reads better. (``NoneType`` - is available as :py:data:`types.NoneType` on Python 3.10+, but is + is available as :py:class:`types.NoneType` on Python 3.10+, but is not exposed at all on earlier versions of Python.) .. note:: diff --git a/docs/source/more_types.rst b/docs/source/more_types.rst index 1febdcac9920a..444753757aad9 100644 --- a/docs/source/more_types.rst +++ b/docs/source/more_types.rst @@ -573,7 +573,7 @@ implementation, then the body is not type checked. If you want to force mypy to check the body anyways, use the :option:`--check-untyped-defs ` flag (:ref:`more details here `). -The variants must also also be compatible with the implementation +The variants must also be compatible with the implementation type hints. In the ``MyList`` example, mypy will check that the parameter type ``int`` and the return type ``T`` are compatible with ``int | slice`` and ``T | Sequence`` for the diff --git a/docs/source/typed_dict.rst b/docs/source/typed_dict.rst index d42b434b05b2d..a0d2dc81e1648 100644 --- a/docs/source/typed_dict.rst +++ b/docs/source/typed_dict.rst @@ -81,9 +81,9 @@ arbitrarily complex types. For example, you can define nested ``TypedDict``\s and containers with ``TypedDict`` items. Unlike most other types, mypy uses structural compatibility checking (or structural subtyping) with ``TypedDict``\s. A ``TypedDict`` object with -extra items is compatible with (a subtype of) a narrower +extra items can be compatible with (a subtype of) a narrower ``TypedDict``, assuming item types are compatible (*totality* also affects -subtyping, as discussed below). +subtyping, as does *closing*, as discussed below). A ``TypedDict`` object is not a subtype of the regular ``dict[...]`` type (and vice versa), since :py:class:`dict` allows arbitrary keys to be @@ -276,6 +276,84 @@ vary :ref:`covariantly `: m: Movie = {"name": "Jaws", "year": 1975} process_entry(m) # OK +You can override a read-only item with a compatible subtype, make a +read-only item mutable, and inherit from multiple parents with compatible +definitions: + +.. code-block:: python + + from collections.abc import Collection, Sequence + + class Competition(TypedDict): + hosts: ReadOnly[Collection[str]] + entries: ReadOnly[Sequence[Entry]] + + class MovieShow(TypedDict): + entries: list[Movie] + + class Oscars(Competition, MovieShow): + hosts: set[str] + +Defining ``hosts`` as a mutable ``set[str]`` item works as this is compatible +with the read-only ``Collection[str]`` definition in ``Competition``. +``entries`` will be of type ``list[Movie]``, taken from the ``MovieShow`` type, +as it is the only non-readonly definition, and is compatible with the definition +in ``Competition``. + +If an item is only defined in supertypes, and is always read-only, mypy takes +the definition from the first parent in the inheritance order, and raises an +error if any other parent definition is incompatible: + +.. code-block:: python + + class NameIds(TypedDict): + ids: ReadOnly[Collection[str]] + + class OrderedIds(TypedDict): + ids: ReadOnly[Sequence[int | str]] + + class OrderedNameIds(NameIds, OrderedIds): + pass # Error! Parent definitions incompatible + +In this example, the definition of ``ids`` will be taken from ``NameIds``, +which would not be compatible with the definition in ``OrderedIds``; reordering +the parents would not solve the problem. Instead, you will need to make a +compatible definition explicitly: + +.. code-block:: python + + class OrderedNameIds(NameIds, OrderedIds): + ids: ReadOnly[Sequence[str]] + +Closing +------- + +You can use the ``closed`` keyword, introduced to ``TypedDict`` in Python +3.15 (and available via ``typing_extensions.TypedDict`` in older +versions) to prevent structural subtypes from adding extra keys to a +type (:pep:`728`): + +.. code-block:: python + + HasName = TypedDict("HasName", {"name": str}) + HasOnlyName = TypedDict("HasOnlyName", {"name": str}, closed=True) + Movie = TypedDict("Movie", {"name": str, "year": int}) + + movie: Movie = {"name": "Nimona", "year": 2023} + has_name: HasName = movie # OK: type is open + has_only_name: HasOnlyName = movie # Error: type is closed + +This allows the typechecker to determine that certain operations are safe, +when they otherwise wouldn't be due to the potential presence of unknown +keys. + +The ``closed`` keyword can also be used in class-based syntax: + +.. code-block:: python + + class HasOnlyName(TypedDict, closed=True): + name: str + Unions of TypedDicts -------------------- @@ -289,6 +367,40 @@ need to give each TypedDict the same key where each value has a unique :ref:`Literal type `. Then, check that key to distinguish between your TypedDicts. +Alternatively, you can implement tagged unions with single-key wrapper dictionaries: + +.. code-block:: python + + class Book(TypedDict): + name: str + length: int + ... + + class DVD(TypedDict): + name: str + length: int + ... + + TaggedBook = TypedDict('TaggedBook', {'book': Book}, closed=True) + TaggedDVD = TypedDict('TaggedDVD', {'dvd': DVD}, closed=True) + type Inventory = TaggedBook | TaggedDVD + + def print_length(inventory: Inventory) -> None: + if "book" in inventory: + print(inventory["book"]["length"], 'pages') + else: + print(inventory["dvd"]["length"], 'minutes') + +Here, the ``closed`` keyword is necessary to allow the ``if`` guard to safely +narrow the types; without it, there could be a structural subtype of ``TaggedDVD`` +that contains a ``book`` field of arbitrary type. + +.. note:: + + Applying ``@final`` to a TypedDict is a legacy way of marking it as closed + for the purposes of type narrowing. It was never fully implemented and is + now superseded; it may be removed in future. + Inline TypedDict types ---------------------- diff --git a/misc/dump-ast.py b/misc/dump-ast.py index 68ea8bc0dc61e..7fdf905bae0b4 100755 --- a/misc/dump-ast.py +++ b/misc/dump-ast.py @@ -19,7 +19,7 @@ def dump(fname: str, python_version: tuple[int, int], quiet: bool = False) -> No options.python_version = python_version with open(fname, "rb") as f: s = f.read() - tree = parse(s, fname, None, errors=Errors(options), options=options, file_exists=True) + tree = parse(s, fname, None, errors=Errors(options), options=options) if not quiet: print(tree) diff --git a/misc/typeshed_patches/0001-Adjust-stubs-to-fix-mypy-lookup-error-due-to-circula.patch b/misc/typeshed_patches/0001-Adjust-stubs-to-fix-mypy-lookup-error-due-to-circula.patch index d8c4e24af917b..87aed9de2bf78 100644 --- a/misc/typeshed_patches/0001-Adjust-stubs-to-fix-mypy-lookup-error-due-to-circula.patch +++ b/misc/typeshed_patches/0001-Adjust-stubs-to-fix-mypy-lookup-error-due-to-circula.patch @@ -1,23 +1,86 @@ -From 5c922a2484f2e18c7f901e62bb499b6414cf1090 Mon Sep 17 00:00:00 2001 +From 8985351bf6f917db1a7b15ffed95ec2357d9819d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> -Date: Wed, 18 Feb 2026 13:11:02 +0100 +Date: Mon, 18 May 2026 17:33:09 +0200 Subject: [PATCH] Adjust stubs to fix mypy lookup error due to circular dependencies in stubs --- - mypy/typeshed/stdlib/time.pyi | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) + mypy/typeshed/stdlib/__future__.pyi | 2 +- + mypy/typeshed/stdlib/dis.pyi | 4 ++-- + mypy/typeshed/stdlib/inspect.pyi | 4 ++-- + mypy/typeshed/stdlib/itertools.pyi | 2 +- + mypy/typeshed/stdlib/time.pyi | 6 +++--- + 5 files changed, 9 insertions(+), 9 deletions(-) +diff --git a/mypy/typeshed/stdlib/__future__.pyi b/mypy/typeshed/stdlib/__future__.pyi +index aa445d22b..a90cf1edd 100644 +--- a/mypy/typeshed/stdlib/__future__.pyi ++++ b/mypy/typeshed/stdlib/__future__.pyi +@@ -1,4 +1,4 @@ +-from typing import TypeAlias ++from typing_extensions import TypeAlias + + _VersionInfo: TypeAlias = tuple[int, int, int, str, int] + +diff --git a/mypy/typeshed/stdlib/dis.pyi b/mypy/typeshed/stdlib/dis.pyi +index 984f932e3..0ad928934 100644 +--- a/mypy/typeshed/stdlib/dis.pyi ++++ b/mypy/typeshed/stdlib/dis.pyi +@@ -2,7 +2,7 @@ import sys + import types + from collections.abc import Callable, Iterator + from opcode import * # `dis` re-exports it as a part of public API +-from typing import IO, Any, Final, NamedTuple, TypeAlias, overload ++from typing import IO, Any, Final, NamedTuple, overload + from typing_extensions import Self, deprecated, disjoint_base + + __all__ = [ +@@ -41,7 +41,7 @@ else: + + # Strictly this should not have to include Callable, but mypy doesn't use FunctionType + # for functions (python/mypy#3171) +-_HaveCodeType: TypeAlias = types.MethodType | types.FunctionType | types.CodeType | type | Callable[..., Any] ++_HaveCodeType = types.MethodType | types.FunctionType | types.CodeType | type | Callable[..., Any] + + if sys.version_info >= (3, 11): + class Positions(NamedTuple): +diff --git a/mypy/typeshed/stdlib/inspect.pyi b/mypy/typeshed/stdlib/inspect.pyi +index cc39a7c9f..0ca4cff75 100644 +--- a/mypy/typeshed/stdlib/inspect.pyi ++++ b/mypy/typeshed/stdlib/inspect.pyi +@@ -196,8 +196,8 @@ if sys.version_info >= (3, 14): + + modulesbyfile: dict[str, Any] + +-_GetMembersPredicateTypeGuard: TypeAlias = Callable[[Any], TypeGuard[_T]] +-_GetMembersPredicateTypeIs: TypeAlias = Callable[[Any], TypeIs[_T]] ++_GetMembersPredicateTypeGuard = Callable[[Any], TypeGuard[_T]] ++_GetMembersPredicateTypeIs = Callable[[Any], TypeIs[_T]] + _GetMembersPredicate: TypeAlias = Callable[[Any], bool] + _GetMembersReturn: TypeAlias = list[tuple[str, _T]] + +diff --git a/mypy/typeshed/stdlib/itertools.pyi b/mypy/typeshed/stdlib/itertools.pyi +index d26a4e1da..60e79bc2e 100644 +--- a/mypy/typeshed/stdlib/itertools.pyi ++++ b/mypy/typeshed/stdlib/itertools.pyi +@@ -23,7 +23,7 @@ _T10 = TypeVar("_T10") + + _Step: TypeAlias = SupportsFloat | SupportsInt | SupportsIndex | SupportsComplex + +-_Predicate: TypeAlias = Callable[[_T], object] ++_Predicate = Callable[[_T], object] + + # Technically count can take anything that implements a number protocol and has an add method + # but we can't enforce the add method diff --git a/mypy/typeshed/stdlib/time.pyi b/mypy/typeshed/stdlib/time.pyi -index 64a009318..d0853792b 100644 +index ac53089b8..9b5344b32 100644 --- a/mypy/typeshed/stdlib/time.pyi +++ b/mypy/typeshed/stdlib/time.pyi -@@ -1,16 +1,16 @@ +@@ -1,15 +1,15 @@ import sys from _typeshed import structseq --from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, final, type_check_only -+from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, Union, final, type_check_only - from typing_extensions import TypeAlias +-from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, TypeAlias, final, type_check_only ++from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, TypeAlias, Union, final, type_check_only _TimeTuple: TypeAlias = tuple[int, int, int, int, int, int, int, int, int] @@ -33,5 +96,5 @@ index 64a009318..d0853792b 100644 altzone: int daylight: int -- -2.53.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Partially-revert-Clean-up-argparse-hacks.patch b/misc/typeshed_patches/0001-Partially-revert-Clean-up-argparse-hacks.patch index a1b1966fea1e0..bd7cbae517543 100644 --- a/misc/typeshed_patches/0001-Partially-revert-Clean-up-argparse-hacks.patch +++ b/misc/typeshed_patches/0001-Partially-revert-Clean-up-argparse-hacks.patch @@ -1,4 +1,4 @@ -From b6d495c10e79fb56ff64f8fb90c87894090f9cbe Mon Sep 17 00:00:00 2001 +From 3fa6658041d3ccd7f2a4bd725e21fa9768bf6ebc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:11:06 +0100 Subject: [PATCH] Partially revert Clean up argparse hacks @@ -8,16 +8,16 @@ Subject: [PATCH] Partially revert Clean up argparse hacks 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/typeshed/stdlib/argparse.pyi b/mypy/typeshed/stdlib/argparse.pyi -index ae99eb036..c87b8f4fc 100644 +index 22d330a08..fa22f842d 100644 --- a/mypy/typeshed/stdlib/argparse.pyi +++ b/mypy/typeshed/stdlib/argparse.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import SupportsWrite, sentinel from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern --from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeVar, overload, type_check_only -+from typing import IO, Any, ClassVar, Final, Generic, NewType, NoReturn, Protocol, TypeVar, overload, type_check_only - from typing_extensions import Self, TypeAlias, deprecated +-from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only ++from typing import IO, Any, ClassVar, Final, Generic, NewType, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only + from typing_extensions import Self, deprecated __all__ = [ @@ -36,7 +36,9 @@ ONE_OR_MORE: Final = "+" @@ -41,5 +41,5 @@ index ae99eb036..c87b8f4fc 100644 default: Any = ..., type: _ActionType = ..., -- -2.52.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch b/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch index 3ca8f4deec922..6794731274d14 100644 --- a/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch +++ b/misc/typeshed_patches/0001-Remove-use-of-LiteralString-in-builtins-13743.patch @@ -1,80 +1,94 @@ -From b3021cdc4b4c2cf547c5bc2d1d21a5a00ffea001 Mon Sep 17 00:00:00 2001 +From 83cf9b2dcc569149510c3a9118b9a7dc44144232 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 26 Sep 2022 12:55:07 -0700 Subject: [PATCH] Remove use of LiteralString in builtins (#13743) --- - mypy/typeshed/stdlib/builtins.pyi | 98 ------------------------------- - 1 file changed, 98 deletions(-) + mypy/typeshed/stdlib/builtins.pyi | 127 +----------------------------- + 1 file changed, 1 insertion(+), 126 deletions(-) diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi -index efc51fe25..8600a372b 100644 +index 0fe6d9a69..1808e28e2 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi -@@ -64,7 +64,6 @@ from typing import ( # noqa: Y022,UP035 - from typing_extensions import ( # noqa: Y023 - Concatenate, - Literal, -- LiteralString, - ParamSpec, - Self, - TypeAlias, -@@ -482,31 +481,16 @@ class str(Sequence[str]): +@@ -65,7 +65,7 @@ from typing import ( # noqa: Y022,UP035 + ) + + # we can't import `Literal` from typing or mypy crashes: see #11247 +-from typing_extensions import Literal, LiteralString, Self, TypeIs, TypeVarTuple, deprecated, disjoint_base # noqa: Y023, UP035 ++from typing_extensions import Literal, Self, TypeIs, TypeVarTuple, deprecated, disjoint_base # noqa: Y023, UP035 + + if sys.version_info >= (3, 14): + from _typeshed import AnnotateFunc +@@ -489,20 +489,8 @@ class str(Sequence[str]): def __new__(cls, object: object = "") -> Self: ... @overload def __new__(cls, object: ReadableBuffer, encoding: str = "utf-8", errors: str = "strict") -> Self: ... +- - @overload - def capitalize(self: LiteralString) -> LiteralString: ... - @overload def capitalize(self) -> str: ... # type: ignore[misc] +- - @overload - def casefold(self: LiteralString) -> LiteralString: ... - @overload def casefold(self) -> str: ... # type: ignore[misc] +- - @overload - def center(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... - @overload def center(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] + def count(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... - def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... +@@ -510,17 +498,9 @@ class str(Sequence[str]): def endswith( self, suffix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, / ) -> bool: ... +- - @overload - def expandtabs(self: LiteralString, tabsize: SupportsIndex = 8) -> LiteralString: ... - @overload def expandtabs(self, tabsize: SupportsIndex = 8) -> str: ... # type: ignore[misc] + def find(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... +- - @overload - def format(self: LiteralString, *args: LiteralString, **kwargs: LiteralString) -> LiteralString: ... - @overload def format(self, *args: object, **kwargs: object) -> str: ... + def format_map(self, mapping: _FormatMapMapping, /) -> str: ... - def index(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... -@@ -522,98 +506,34 @@ class str(Sequence[str]): +@@ -537,119 +517,38 @@ class str(Sequence[str]): def isspace(self) -> bool: ... def istitle(self) -> bool: ... def isupper(self) -> bool: ... +- - @overload - def join(self: LiteralString, iterable: Iterable[LiteralString], /) -> LiteralString: ... - @overload def join(self, iterable: Iterable[str], /) -> str: ... # type: ignore[misc] +- - @overload - def ljust(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... - @overload def ljust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] +- - @overload - def lower(self: LiteralString) -> LiteralString: ... - @overload def lower(self) -> str: ... # type: ignore[misc] +- - @overload - def lstrip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... - @overload def lstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- - @overload - def partition(self: LiteralString, sep: LiteralString, /) -> tuple[LiteralString, LiteralString, LiteralString]: ... - @overload def partition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] + if sys.version_info >= (3, 13): - @overload - def replace( @@ -94,102 +108,127 @@ index efc51fe25..8600a372b 100644 - def removeprefix(self: LiteralString, prefix: LiteralString, /) -> LiteralString: ... - @overload def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc] +- - @overload - def removesuffix(self: LiteralString, suffix: LiteralString, /) -> LiteralString: ... - @overload def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc] + def rfind(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def rindex(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... +- - @overload - def rjust(self: LiteralString, width: SupportsIndex, fillchar: LiteralString = " ", /) -> LiteralString: ... - @overload def rjust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] +- - @overload - def rpartition(self: LiteralString, sep: LiteralString, /) -> tuple[LiteralString, LiteralString, LiteralString]: ... - @overload def rpartition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] +- - @overload - def rsplit(self: LiteralString, sep: LiteralString | None = None, maxsplit: SupportsIndex = -1) -> list[LiteralString]: ... - @overload def rsplit(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] +- - @overload - def rstrip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... - @overload def rstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- - @overload - def split(self: LiteralString, sep: LiteralString | None = None, maxsplit: SupportsIndex = -1) -> list[LiteralString]: ... - @overload def split(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] +- - @overload - def splitlines(self: LiteralString, keepends: bool = False) -> list[LiteralString]: ... - @overload def splitlines(self, keepends: bool = False) -> list[str]: ... # type: ignore[misc] + def startswith( self, prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, / ) -> bool: ... +- - @overload - def strip(self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString: ... - @overload def strip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] +- - @overload - def swapcase(self: LiteralString) -> LiteralString: ... - @overload def swapcase(self) -> str: ... # type: ignore[misc] +- - @overload - def title(self: LiteralString) -> LiteralString: ... - @overload def title(self) -> str: ... # type: ignore[misc] + def translate(self, table: _TranslateTable, /) -> str: ... +- - @overload - def upper(self: LiteralString) -> LiteralString: ... - @overload def upper(self) -> str: ... # type: ignore[misc] +- - @overload - def zfill(self: LiteralString, width: SupportsIndex, /) -> LiteralString: ... - @overload def zfill(self, width: SupportsIndex, /) -> str: ... # type: ignore[misc] - @staticmethod - @overload -@@ -624,39 +544,21 @@ class str(Sequence[str]): + + if sys.version_info >= (3, 15): +@@ -677,49 +576,25 @@ class str(Sequence[str]): @staticmethod @overload def maketrans(x: str, y: str, z: str, /) -> dict[int, int | None]: ... +- - @overload - def __add__(self: LiteralString, value: LiteralString, /) -> LiteralString: ... - @overload def __add__(self, value: str, /) -> str: ... # type: ignore[misc] + # Incompatible with Sequence.__contains__ def __contains__(self, key: str, /) -> bool: ... # type: ignore[override] def __eq__(self, value: object, /) -> bool: ... def __ge__(self, value: str, /) -> bool: ... +- - @overload - def __getitem__(self: LiteralString, key: SupportsIndex | slice[SupportsIndex | None], /) -> LiteralString: ... - @overload def __getitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> str: ... # type: ignore[misc] + def __gt__(self, value: str, /) -> bool: ... def __hash__(self) -> int: ... +- - @overload - def __iter__(self: LiteralString) -> Iterator[LiteralString]: ... - @overload def __iter__(self) -> Iterator[str]: ... # type: ignore[misc] + def __le__(self, value: str, /) -> bool: ... def __len__(self) -> int: ... def __lt__(self, value: str, /) -> bool: ... +- - @overload - def __mod__(self: LiteralString, value: LiteralString | tuple[LiteralString, ...], /) -> LiteralString: ... - @overload def __mod__(self, value: Any, /) -> str: ... +- - @overload - def __mul__(self: LiteralString, value: SupportsIndex, /) -> LiteralString: ... - @overload def __mul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __ne__(self, value: object, /) -> bool: ... +- - @overload - def __rmul__(self: LiteralString, value: SupportsIndex, /) -> LiteralString: ... - @overload def __rmul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __getnewargs__(self) -> tuple[str]: ... - def __format__(self, format_spec: str, /) -> str: ... -- -2.52.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-Remove-redundant-inheritances-from-Iterator.patch b/misc/typeshed_patches/0001-Revert-Remove-redundant-inheritances-from-Iterator.patch index 6194dc62cd36e..96779922b52f6 100644 --- a/misc/typeshed_patches/0001-Revert-Remove-redundant-inheritances-from-Iterator.patch +++ b/misc/typeshed_patches/0001-Revert-Remove-redundant-inheritances-from-Iterator.patch @@ -1,4 +1,4 @@ -From 69791281c2c5e919cea9a77c4a771f79d9e70384 Mon Sep 17 00:00:00 2001 +From d3dabcf7b7aaf0997b59f0a28bb41d17b6098f2c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:36:38 +0100 Subject: [PATCH] Revert Remove redundant inheritances from Iterator in @@ -15,7 +15,7 @@ Subject: [PATCH] Revert Remove redundant inheritances from Iterator in 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/mypy/typeshed/stdlib/_asyncio.pyi b/mypy/typeshed/stdlib/_asyncio.pyi -index d663f5d93..f43178e4d 100644 +index 23b690a9c..0ce93a435 100644 --- a/mypy/typeshed/stdlib/_asyncio.pyi +++ b/mypy/typeshed/stdlib/_asyncio.pyi @@ -1,6 +1,6 @@ @@ -25,7 +25,7 @@ index d663f5d93..f43178e4d 100644 +from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable from contextvars import Context from types import FrameType, GenericAlias - from typing import Any, Literal, TextIO, TypeVar + from typing import Any, Literal, TextIO, TypeAlias, TypeVar @@ -11,7 +11,7 @@ _T_co = TypeVar("_T_co", covariant=True) _TaskYieldType: TypeAlias = Future[object] | None @@ -36,10 +36,10 @@ index d663f5d93..f43178e4d 100644 @property def _exception(self) -> BaseException | None: ... diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi -index 8acdeadff..5a3bb5908 100644 +index 1808e28e2..9db6f4fc7 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi -@@ -1226,7 +1226,7 @@ class frozenset(AbstractSet[_T_co]): +@@ -1369,7 +1369,7 @@ class frozenset(AbstractSet[_T_co]): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @disjoint_base @@ -48,7 +48,7 @@ index 8acdeadff..5a3bb5908 100644 def __new__(cls, iterable: Iterable[_T], start: int = 0) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> tuple[int, _T]: ... -@@ -1413,7 +1413,7 @@ else: +@@ -1635,7 +1635,7 @@ else: exit: _sitebuiltins.Quitter @disjoint_base @@ -57,16 +57,16 @@ index 8acdeadff..5a3bb5908 100644 @overload def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... @overload -@@ -1477,7 +1477,7 @@ license: _sitebuiltins._Printer - +@@ -1706,7 +1706,7 @@ license: _sitebuiltins._Printer def locals() -> dict[str, Any]: ... + @disjoint_base -class map(Generic[_S]): +class map(Iterator[_S]): # 3.14 adds `strict` argument. if sys.version_info >= (3, 14): @overload -@@ -1784,7 +1784,7 @@ def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = None) -> complex +@@ -2022,7 +2022,7 @@ def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = None) -> complex quit: _sitebuiltins.Quitter @disjoint_base @@ -75,29 +75,29 @@ index 8acdeadff..5a3bb5908 100644 @overload def __new__(cls, sequence: Reversible[_T], /) -> Iterator[_T]: ... # type: ignore[misc] @overload -@@ -1848,7 +1848,7 @@ def vars(object: type, /) -> types.MappingProxyType[str, Any]: ... - @overload +@@ -2100,7 +2100,7 @@ def vars(object: type, /) -> types.MappingProxyType[str, Any]: ... def vars(object: Any = ..., /) -> dict[str, Any]: ... + @disjoint_base -class zip(Generic[_T_co]): +class zip(Iterator[_T_co]): - if sys.version_info >= (3, 10): - @overload - def __new__(cls, *, strict: bool = False) -> zip[Any]: ... + @overload + def __new__(cls, *, strict: bool = False) -> zip[Any]: ... + @overload diff --git a/mypy/typeshed/stdlib/csv.pyi b/mypy/typeshed/stdlib/csv.pyi -index 2c8e7109c..4ed0ab1d8 100644 +index f8ab5f000..f3b4286a6 100644 --- a/mypy/typeshed/stdlib/csv.pyi +++ b/mypy/typeshed/stdlib/csv.pyi -@@ -25,7 +25,7 @@ else: - from _csv import _reader as Reader, _writer as Writer - +@@ -21,7 +21,7 @@ if sys.version_info >= (3, 12): + from _csv import QUOTE_NOTNULL as QUOTE_NOTNULL, QUOTE_STRINGS as QUOTE_STRINGS + from _csv import Reader, Writer from _typeshed import SupportsWrite -from collections.abc import Collection, Iterable, Mapping, Sequence +from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence from types import GenericAlias from typing import Any, Generic, Literal, TypeVar, overload from typing_extensions import Self -@@ -73,7 +73,7 @@ class excel(Dialect): ... +@@ -69,7 +69,7 @@ class excel(Dialect): ... class excel_tab(excel): ... class unix_dialect(Dialect): ... @@ -107,7 +107,7 @@ index 2c8e7109c..4ed0ab1d8 100644 restkey: _T | None restval: str | Any | None diff --git a/mypy/typeshed/stdlib/fileinput.pyi b/mypy/typeshed/stdlib/fileinput.pyi -index 6778b7648..95164de2f 100644 +index 37783254c..db9c228f5 100644 --- a/mypy/typeshed/stdlib/fileinput.pyi +++ b/mypy/typeshed/stdlib/fileinput.pyi @@ -1,8 +1,8 @@ @@ -116,22 +116,22 @@ index 6778b7648..95164de2f 100644 -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Iterator from types import GenericAlias, TracebackType --from typing import IO, Any, AnyStr, Generic, Literal, Protocol, overload, type_check_only -+from typing import IO, Any, AnyStr, Literal, Protocol, overload, type_check_only - from typing_extensions import Self, TypeAlias, deprecated +-from typing import IO, Any, AnyStr, Generic, Literal, Protocol, TypeAlias, overload, type_check_only ++from typing import IO, Any, AnyStr, Literal, Protocol, TypeAlias, overload, type_check_only + from typing_extensions import Self, deprecated __all__ = [ -@@ -105,7 +105,7 @@ def fileno() -> int: ... +@@ -74,7 +74,7 @@ def fileno() -> int: ... def isfirstline() -> bool: ... def isstdin() -> bool: ... -class FileInput(Generic[AnyStr]): +class FileInput(Iterator[AnyStr]): - if sys.version_info >= (3, 10): - # encoding and errors are added - @overload + # encoding and errors are added + @overload + def __init__( diff --git a/mypy/typeshed/stdlib/itertools.pyi b/mypy/typeshed/stdlib/itertools.pyi -index 8a924ad8b..5c2bf7f83 100644 +index d26a4e1da..9d1cd84da 100644 --- a/mypy/typeshed/stdlib/itertools.pyi +++ b/mypy/typeshed/stdlib/itertools.pyi @@ -28,7 +28,7 @@ _Predicate: TypeAlias = Callable[[_T], object] @@ -143,7 +143,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls) -> count[int]: ... @overload -@@ -39,13 +39,13 @@ class count(Generic[_N]): +@@ -40,13 +40,13 @@ class count(Generic[_N]): def __iter__(self) -> Self: ... @disjoint_base @@ -159,7 +159,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, object: _T) -> Self: ... @overload -@@ -55,7 +55,7 @@ class repeat(Generic[_T]): +@@ -57,7 +57,7 @@ class repeat(Generic[_T]): def __length_hint__(self) -> int: ... @disjoint_base @@ -168,7 +168,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T], func: None = None, *, initial: _T | None = None) -> Self: ... @overload -@@ -64,7 +64,7 @@ class accumulate(Generic[_T]): +@@ -67,7 +67,7 @@ class accumulate(Generic[_T]): def __next__(self) -> _T: ... @disjoint_base @@ -177,7 +177,7 @@ index 8a924ad8b..5c2bf7f83 100644 def __new__(cls, *iterables: Iterable[_T]) -> Self: ... def __next__(self) -> _T: ... def __iter__(self) -> Self: ... -@@ -74,25 +74,25 @@ class chain(Generic[_T]): +@@ -77,25 +77,25 @@ class chain(Generic[_T]): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @disjoint_base @@ -207,7 +207,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T1], key: None = None) -> groupby[_T1, _T1]: ... @overload -@@ -101,7 +101,7 @@ class groupby(Generic[_T_co, _S_co]): +@@ -105,7 +105,7 @@ class groupby(Generic[_T_co, _S_co]): def __next__(self) -> tuple[_T_co, Iterator[_S_co]]: ... @disjoint_base @@ -216,7 +216,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T], stop: int | None, /) -> Self: ... @overload -@@ -110,20 +110,20 @@ class islice(Generic[_T]): +@@ -115,13 +115,13 @@ class islice(Generic[_T]): def __next__(self) -> _T: ... @disjoint_base @@ -232,15 +232,16 @@ index 8a924ad8b..5c2bf7f83 100644 def __new__(cls, predicate: _Predicate[_T], iterable: Iterable[_T], /) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> _T: ... - +@@ -129,7 +129,7 @@ class takewhile(Generic[_T]): def tee(iterable: Iterable[_T], n: int = 2, /) -> tuple[Iterator[_T], ...]: ... + @disjoint_base -class zip_longest(Generic[_T_co]): +class zip_longest(Iterator[_T_co]): # one iterable (fillvalue doesn't matter) @overload def __new__(cls, iter1: Iterable[_T1], /, *, fillvalue: object = None) -> zip_longest[tuple[_T1]]: ... -@@ -202,7 +202,7 @@ class zip_longest(Generic[_T_co]): +@@ -209,7 +209,7 @@ class zip_longest(Generic[_T_co]): def __next__(self) -> _T_co: ... @disjoint_base @@ -249,7 +250,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iter1: Iterable[_T1], /) -> product[tuple[_T1]]: ... @overload -@@ -288,7 +288,7 @@ class product(Generic[_T_co]): +@@ -296,7 +296,7 @@ class product(Generic[_T_co]): def __next__(self) -> _T_co: ... @disjoint_base @@ -258,7 +259,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> permutations[tuple[_T, _T]]: ... @overload -@@ -303,7 +303,7 @@ class permutations(Generic[_T_co]): +@@ -312,7 +312,7 @@ class permutations(Generic[_T_co]): def __next__(self) -> _T_co: ... @disjoint_base @@ -267,7 +268,7 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> combinations[tuple[_T, _T]]: ... @overload -@@ -318,7 +318,7 @@ class combinations(Generic[_T_co]): +@@ -328,7 +328,7 @@ class combinations(Generic[_T_co]): def __next__(self) -> _T_co: ... @disjoint_base @@ -276,15 +277,15 @@ index 8a924ad8b..5c2bf7f83 100644 @overload def __new__(cls, iterable: Iterable[_T], r: Literal[2]) -> combinations_with_replacement[tuple[_T, _T]]: ... @overload -@@ -334,14 +334,14 @@ class combinations_with_replacement(Generic[_T_co]): +@@ -344,14 +344,14 @@ class combinations_with_replacement(Generic[_T_co]): + def __next__(self) -> _T_co: ... - if sys.version_info >= (3, 10): - @disjoint_base -- class pairwise(Generic[_T_co]): -+ class pairwise(Iterator[_T_co]): - def __new__(cls, iterable: Iterable[_T], /) -> pairwise[tuple[_T, _T]]: ... - def __iter__(self) -> Self: ... - def __next__(self) -> _T_co: ... + @disjoint_base +-class pairwise(Generic[_T_co]): ++class pairwise(Iterator[_T_co]): + def __new__(cls, iterable: Iterable[_T], /) -> pairwise[tuple[_T, _T]]: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _T_co: ... if sys.version_info >= (3, 12): @disjoint_base @@ -313,10 +314,10 @@ index b79f9e773..f276372d0 100644 def __iter__(self) -> Self: ... def next(self, timeout: float | None = None) -> _T: ... diff --git a/mypy/typeshed/stdlib/sqlite3/__init__.pyi b/mypy/typeshed/stdlib/sqlite3/__init__.pyi -index 04b978b1b..e4604144f 100644 +index 7c033bdf4..7bf020558 100644 --- a/mypy/typeshed/stdlib/sqlite3/__init__.pyi +++ b/mypy/typeshed/stdlib/sqlite3/__init__.pyi -@@ -408,7 +408,7 @@ class Connection: +@@ -426,7 +426,7 @@ class Connection: ) -> Literal[False]: ... @disjoint_base @@ -326,5 +327,5 @@ index 04b978b1b..e4604144f 100644 @property def connection(self) -> Connection: ... -- -2.52.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-Use-contravariant-type-variable-in-Container.patch b/misc/typeshed_patches/0001-Revert-Use-contravariant-type-variable-in-Container.patch index 814645b7f09d1..e31001589faf9 100644 --- a/misc/typeshed_patches/0001-Revert-Use-contravariant-type-variable-in-Container.patch +++ b/misc/typeshed_patches/0001-Revert-Use-contravariant-type-variable-in-Container.patch @@ -1,4 +1,4 @@ -From 274a77fe74d961e9885036ed31e5ed46f8d91daa Mon Sep 17 00:00:00 2001 +From 1b131f8f39532078597f51143e74e07e6cc95dec Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:00:18 +0100 Subject: [PATCH] Revert Use contravariant type variable in Container @@ -8,11 +8,11 @@ Subject: [PATCH] Revert Use contravariant type variable in Container 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi -index 83e114aac..e9f67877c 100644 +index 2018a835c..2379bec34 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi -@@ -640,17 +640,14 @@ class AsyncGenerator(AsyncIterator[_YieldT_co], Protocol[_YieldT_co, _SendT_cont - ) -> Coroutine[Any, Any, _YieldT_co]: ... +@@ -663,17 +663,14 @@ class AsyncGenerator(AsyncIterator[_YieldT_co], Protocol[_YieldT_co, _SendT_cont + def aclose(self) -> Coroutine[Any, Any, None]: ... -_ContainerT_contra = TypeVar("_ContainerT_contra", contravariant=True, default=Any) @@ -33,5 +33,5 @@ index 83e114aac..e9f67877c 100644 @abstractmethod def __len__(self) -> int: ... -- -2.53.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch b/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch index 890b2782abedd..f144e989fb683 100644 --- a/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch +++ b/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch @@ -1,30 +1,63 @@ -From d88a4b774fc59ecd0f2d0fdda8e0e61092adafd3 Mon Sep 17 00:00:00 2001 +From 92a0169fa37f3ff1c41beb98ae0f61e3b3ff19dc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 8 Apr 2026 00:01:44 +0100 Subject: [PATCH] Revert dict.__or__ typeshed change --- - mypy/typeshed/stdlib/builtins.pyi | 6 ++++++ - 1 file changed, 6 insertions(+) + mypy/typeshed/stdlib/builtins.pyi | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi -index 674142d70..25b21ba97 100644 +index 9db6f4fc7..7ec0e2d7c 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi -@@ -1142,7 +1142,13 @@ class dict(MutableMapping[_KT, _VT]): - def __reversed__(self) -> Iterator[_KT]: ... +@@ -1228,14 +1228,27 @@ class dict(MutableMapping[_KT, _VT]): __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -+ @overload -+ def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... -+ @overload - def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... -+ @overload -+ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... -+ @overload - def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + if sys.version_info >= (3, 15): ++ @overload ++ def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> dict[_KT, _VT]: ... ++ @overload + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + ++ @overload ++ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload ++ def __ror__(self, value: frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... ++ @overload + def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + else: ++ @overload ++ def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... ++ @overload + def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... ++ @overload ++ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... ++ @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + # dict.__ior__ should be kept roughly in line with MutableMapping.update() - @overload # type: ignore[misc] +@@ -1290,11 +1303,18 @@ if sys.version_info >= (3, 15): + def __iter__(self) -> Iterator[_KT]: ... + def __hash__(self) -> int: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... ++ @overload ++ def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... ++ @overload + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + ++ @overload ++ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload ++ def __ror__(self, value: frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... ++ @overload + def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + + @disjoint_base -- -2.25.1 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-operator-changes.patch b/misc/typeshed_patches/0001-Revert-operator-changes.patch index 71b0ae9b47abd..77cdeebb619bf 100644 --- a/misc/typeshed_patches/0001-Revert-operator-changes.patch +++ b/misc/typeshed_patches/0001-Revert-operator-changes.patch @@ -1,14 +1,14 @@ -From 7f38b86464d59188a87ff8c9913c257e788a2f0b Mon Sep 17 00:00:00 2001 +From 5ab958030bd7d4ed9cb3e2afa656b68ea5a07901 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Wed, 6 May 2026 19:49:41 -0700 Subject: [PATCH] Revert operator changes --- - mypy/typeshed/stdlib/_operator.pyi | 51 ++++++++---------------------- - 1 file changed, 14 insertions(+), 37 deletions(-) + mypy/typeshed/stdlib/_operator.pyi | 58 ++++++++---------------------- + 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/mypy/typeshed/stdlib/_operator.pyi b/mypy/typeshed/stdlib/_operator.pyi -index e7d85f811..8c705065b 100644 +index 04dae79dc..29a6d7057 100644 --- a/mypy/typeshed/stdlib/_operator.pyi +++ b/mypy/typeshed/stdlib/_operator.pyi @@ -1,15 +1,5 @@ @@ -27,8 +27,8 @@ index e7d85f811..8c705065b 100644 +from _typeshed import SupportsGetItem from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence from operator import attrgetter as attrgetter, itemgetter as itemgetter, methodcaller as methodcaller - from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload, type_check_only -@@ -18,7 +8,6 @@ from typing_extensions import ParamSpec, TypeAlias, TypeIs + from typing import Any, AnyStr, ParamSpec, Protocol, SupportsAbs, SupportsIndex, TypeAlias, TypeVar, overload, type_check_only +@@ -18,7 +8,6 @@ from typing_extensions import TypeIs _R = TypeVar("_R") _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) @@ -36,14 +36,16 @@ index e7d85f811..8c705065b 100644 _K = TypeVar("_K") _V = TypeVar("_V") _P = ParamSpec("_P") -@@ -69,36 +58,24 @@ def truth(a: object, /) -> bool: ... +@@ -69,43 +58,24 @@ def truth(a: object, /) -> bool: ... def is_(a: object, b: object, /) -> bool: ... def is_not(a: object, b: object, /) -> bool: ... def abs(a: SupportsAbs[_T], /) -> _T: ... +- -@overload -def add(a: SupportsAdd[_T_contra, _T_co], b: _T_contra, /) -> _T_co: ... -@overload -def add(a: _T_contra, b: SupportsRAdd[_T_contra, _T_co], /) -> _T_co: ... +- -def and_(a, b, /): ... -def floordiv(a, b, /): ... +def add(a: Any, b: Any, /) -> Any: ... @@ -53,14 +55,17 @@ index e7d85f811..8c705065b 100644 def inv(a: _SupportsInversion[_T_co], /) -> _T_co: ... def invert(a: _SupportsInversion[_T_co], /) -> _T_co: ... -def lshift(a, b, /): ... +- -@overload -def mod(a: SupportsMod[_T_contra, _T_co], b: _T_contra, /) -> _T_co: ... -@overload -def mod(a: _T_contra, b: SupportsRMod[_T_contra, _T_co], /) -> _T_co: ... +- -@overload -def mul(a: SupportsMul[_T_contra, _T_co], b: _T_contra, /) -> _T_co: ... -@overload -def mul(a: _T_contra, b: SupportsRMul[_T_contra, _T_co], /) -> _T_co: ... +- -def matmul(a, b, /): ... +def lshift(a: Any, b: Any, /) -> Any: ... +def mod(a: Any, b: Any, /) -> Any: ... @@ -72,10 +77,12 @@ index e7d85f811..8c705065b 100644 def pos(a: _SupportsPos[_T_co], /) -> _T_co: ... -def pow(a, b, /): ... -def rshift(a, b, /): ... +- -@overload -def sub(a: SupportsSub[_T_contra, _T_co], b: _T_contra, /) -> _T_co: ... -@overload -def sub(a: _T_contra, b: SupportsRSub[_T_contra, _T_co], /) -> _T_co: ... +- -def truediv(a, b, /): ... -def xor(a, b, /): ... +def pow(a: Any, b: Any, /) -> Any: ... @@ -87,5 +94,5 @@ index e7d85f811..8c705065b 100644 def contains(a: Container[object], b: object, /) -> bool: ... def countOf(a: Iterable[object], b: object, /) -> int: ... -- -2.53.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch b/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch index d05f66ff26108..99e4cb0e8d187 100644 --- a/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch +++ b/misc/typeshed_patches/0001-Revert-sum-literal-integer-change-13961.patch @@ -1,4 +1,4 @@ -From 0e30b762e8335f02e19977c055ac7b98e707991c Mon Sep 17 00:00:00 2001 +From a97066922305395b64cbbc1a6f68f7cae60a3aa6 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 29 Oct 2022 12:47:21 -0700 Subject: [PATCH] Revert sum literal integer change (#13961) @@ -19,10 +19,10 @@ within mypy, I might pursue upstreaming this in typeshed. 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi -index 5a3bb5908..0f2196070 100644 +index dc24bc540..6f21960cb 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi -@@ -1835,7 +1835,7 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit +@@ -2099,7 +2099,7 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit # without creating many false-positive errors (see #7578). # Instead, we special-case the most common examples of this: bool and literal integers. @overload @@ -32,5 +32,5 @@ index 5a3bb5908..0f2196070 100644 def sum(iterable: Iterable[_SupportsSumNoDefaultT], /) -> _SupportsSumNoDefaultT | Literal[0]: ... @overload -- -2.52.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch b/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch index e795be53bce71..a19d87ba8c3f8 100644 --- a/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch +++ b/misc/typeshed_patches/0001-Revert-typeshed-ctypes-change.patch @@ -1,4 +1,4 @@ -From 80a710e0e2c09612c6fc30c644f2052450e69ccd Mon Sep 17 00:00:00 2001 +From 22a35cde63e8e89b91b4b62a20f4ef0fc3f5d3fe Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Mon, 1 May 2023 20:34:55 +0100 Subject: [PATCH] Revert typeshed ctypes change @@ -7,26 +7,27 @@ The plugin provides superior type checking: https://github.com/python/mypy/pull/13987#issuecomment-1310863427 A manual cherry-pick of e437cdf. --- - mypy/typeshed/stdlib/_ctypes.pyi | 6 +----- - 1 file changed, 1 insertion(+), 5 deletions(-) + mypy/typeshed/stdlib/_ctypes.pyi | 7 +------ + 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mypy/typeshed/stdlib/_ctypes.pyi b/mypy/typeshed/stdlib/_ctypes.pyi -index be7792818..b7a3fb104 100644 +index d800ac5c2..1efb11926 100644 --- a/mypy/typeshed/stdlib/_ctypes.pyi +++ b/mypy/typeshed/stdlib/_ctypes.pyi -@@ -320,11 +320,7 @@ class Array(_CData, Generic[_CT], metaclass=_PyCArrayType): - def _type_(self) -> type[_CT]: ... +@@ -324,12 +324,7 @@ class Array(_CData, Generic[_CT], metaclass=_PyCArrayType): @_type_.setter def _type_(self, value: type[_CT]) -> None: ... + - # Note: only available if _CT == c_char - @property - def raw(self) -> bytes: ... - @raw.setter - def raw(self, value: ReadableBuffer) -> None: ... +- + raw: bytes # Note: only available if _CT == c_char value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise # TODO: These methods cannot be annotated correctly at the moment. # All of these "Any"s stand for the array's element type, but it's not possible to use _CT -- -2.52.0 +2.54.0 diff --git a/misc/typeshed_patches/0001-Temporarily-revert-contextlib-deprecation.patch b/misc/typeshed_patches/0001-Temporarily-revert-contextlib-deprecation.patch index 4540e891f9954..fab037c38045e 100644 --- a/misc/typeshed_patches/0001-Temporarily-revert-contextlib-deprecation.patch +++ b/misc/typeshed_patches/0001-Temporarily-revert-contextlib-deprecation.patch @@ -1,4 +1,4 @@ -From ae45edc46f2ca9c939e317648b44ed3372487798 Mon Sep 17 00:00:00 2001 +From 12fb8e77eb5efc5c025d961e04ba7ccef23333bf Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 22 Feb 2026 20:37:48 -0800 Subject: [PATCH] Temporarily revert contextlib deprecation @@ -8,19 +8,19 @@ Subject: [PATCH] Temporarily revert contextlib deprecation 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/mypy/typeshed/stdlib/contextlib.pyi b/mypy/typeshed/stdlib/contextlib.pyi -index 8d5902c46..221102ee2 100644 +index b95c23502..73cdda3b8 100644 --- a/mypy/typeshed/stdlib/contextlib.pyi +++ b/mypy/typeshed/stdlib/contextlib.pyi @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator from types import TracebackType - from typing import Any, Generic, Protocol, TypeVar, overload, runtime_checkable, type_check_only --from typing_extensions import ParamSpec, Self, TypeAlias, deprecated -+from typing_extensions import ParamSpec, Self, TypeAlias + from typing import Any, Generic, ParamSpec, Protocol, TypeAlias, TypeVar, overload, runtime_checkable, type_check_only +-from typing_extensions import Self, deprecated ++from typing_extensions import Self __all__ = [ - "contextmanager", -@@ -86,12 +86,6 @@ class _GeneratorContextManager( + "aclosing", +@@ -84,12 +84,6 @@ class _GeneratorContextManager( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> bool | None: ... @@ -32,10 +32,10 @@ index 8d5902c46..221102ee2 100644 -) def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ... - if sys.version_info >= (3, 10): -@@ -118,13 +112,6 @@ else: - self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool | None: ... + _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) +@@ -107,13 +101,6 @@ class _AsyncGeneratorContextManager( + self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool | None: ... -@overload -def asynccontextmanager(func: Callable[_P, AsyncGenerator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ... @@ -45,8 +45,8 @@ index 8d5902c46..221102ee2 100644 - "Use `-> AsyncGenerator[Foo]` instead." -) def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ... + @type_check_only - class _SupportsClose(Protocol): -- -2.53.0 +2.54.0 diff --git a/misc/upload-pypi.py b/misc/upload-pypi.py index 8ea86bbea584b..52092bd288f0f 100644 --- a/misc/upload-pypi.py +++ b/misc/upload-pypi.py @@ -31,16 +31,7 @@ def is_whl_or_tar(name: str) -> bool: def item_ok_for_pypi(name: str) -> bool: - if not is_whl_or_tar(name): - return False - - name = name.removesuffix(".tar.gz") - name = name.removesuffix(".whl") - - if name.endswith("wasm32"): - return False - - return True + return is_whl_or_tar(name) def get_release_for_tag(tag: str) -> dict[str, Any]: diff --git a/mypy-requirements.txt b/mypy-requirements.txt index 27c76a0f3f6a8..87663f2adde40 100644 --- a/mypy-requirements.txt +++ b/mypy-requirements.txt @@ -5,5 +5,5 @@ typing_extensions>=4.14.0; python_version>='3.15' mypy_extensions>=1.0.0 pathspec>=1.0.0 tomli>=1.1.0; python_version<'3.11' -librt>=0.10.0; platform_python_implementation != 'PyPy' -ast-serialize>=0.3.0,<1.0.0 +librt>=0.13.0; platform_python_implementation != 'PyPy' +ast-serialize>=0.6.0,<1.0.0 diff --git a/mypy/applytype.py b/mypy/applytype.py index c8003795ba0b1..fbc93492f87e9 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -37,10 +37,12 @@ def get_target_type( report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None], context: Context, skip_unsatisfied: bool, + id_to_type: dict[TypeVarId, Type], ) -> Type | None: p_type = get_proper_type(type) - if isinstance(p_type, UninhabitedType) and tvar.has_default(): - return tvar.default + if isinstance(p_type, UninhabitedType) and p_type.ambiguous and tvar.has_default(): + # Gradually expand defaults, as they may depend on previous type variables. + return expand_type(tvar.default, id_to_type) if isinstance(tvar, ParamSpecType): return type if isinstance(tvar, TypeVarTupleType): @@ -113,7 +115,13 @@ def apply_generic_arguments( continue target_type = get_target_type( - tvar, type, callable, report_incompatible_typevar_value, context, skip_unsatisfied + tvar, + type, + callable, + report_incompatible_typevar_value, + context, + skip_unsatisfied, + id_to_type, ) if target_type is not None: id_to_type[tvar.id] = target_type @@ -171,11 +179,17 @@ def apply_generic_arguments( assert isinstance(typ, TypeVarLikeType) remaining_tvars.append(typ) + instance_type = None + if callable.instance_type is not None: + instance_type = expand_type(callable.instance_type, id_to_type) + assert isinstance(instance_type, ProperType) + return callable.copy_modified( ret_type=expand_type(callable.ret_type, id_to_type), variables=remaining_tvars, type_guard=type_guard, type_is=type_is, + instance_type=instance_type, ) diff --git a/mypy/build.py b/mypy/build.py index 21a5559b329a3..a03a6eb8972cc 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -230,10 +230,10 @@ def __init__( self.mod_ids = ids # Direct dependencies, should be populated by the caller. self.deps: set[int] = set(deps) if deps is not None else set() - # Direct dependencies that have not been processed yet. - # Should be populated by the caller. This set may change during graph - # processing, while the above stays constant. - self.not_ready_deps: set[int] = set() + # Count of direct dependencies that have not been processed yet. + # Populated by the caller from len(deps); decremented during graph + # processing as each dep completes. self.deps above stays constant. + self.not_ready_count: int = 0 # SCCs that (directly) depend on this SCC. Note this is a list to # make processing order more predictable. Dependents will be notified # that they may be ready in the order in this list. @@ -998,6 +998,10 @@ def __init__( self.import_options: dict[str, bytes] = {} # Cache for transitive dependency check (expensive). self.transitive_deps_cache: dict[tuple[int, int], bool] = {} + # Cache for options_snapshot() keyed by the cloned Options. Options is + # hashed by identity, and most modules share a handful of distinct + # configs, so this collapses ~all calls onto a few entries. + self.options_snapshot_cache: dict[Options, tuple[str, str]] = {} # Packages for which we know presence or absence of __getattr__(). self.known_partial_packages: dict[str, bool] = {} @@ -1024,85 +1028,78 @@ def parse_all(self, states: list[State], post_parse: bool = True) -> None: self.post_parse_all(states) return - sequential_states = [] parallel_states = [] for state in states: + if not self.fscache.exists(state.xpath, real_only=True) or ( + self.shadow_map and self.maybe_swap_for_shadow_path(state.xpath) != state.xpath + ): + state.source = state.get_source() if state.tree is not None: # The file was already parsed. - continue - if not self.fscache.exists(state.xpath, real_only=True): - # New parser only supports parsing on-disk files. - sequential_states.append(state) + state.needs_parse = False continue parallel_states.append(state) + if len(parallel_states) > 1: - self.parse_parallel(sequential_states, parallel_states) - else: - # Avoid using executor when there is no parallelism. - for state in states: - state.parse_file() - if post_parse: - self.post_parse_all(states) + # This duplicates a bit of logic from State.parse_file(). This is done as an + # optimization to parallelize only those parts of the code that can be + # parallelized efficiently. - def parse_parallel(self, sequential_states: list[State], parallel_states: list[State]) -> None: - """Perform parallel parsing of states. + parallel_parsed_states, parallel_parsed_states_set = self.parse_files_threaded_raw( + parallel_states + ) - Note: this duplicates a bit of logic from State.parse_file(). This is done - as an optimization to parallelize only those parts of the code that can be - parallelized efficiently. - """ - parallel_parsed_states, parallel_parsed_states_set = self.parse_files_threaded_raw( - sequential_states, parallel_states - ) + for state in parallel_parsed_states: + # New parser only returns serialized ASTs + with state.wrap_context(): + assert state.tree is not None + raw_data = state.tree.raw_data + if raw_data is not None: + # Apply inline mypy config before deserialization, since + # some options (e.g. implicit_optional) affect how the + # AST is built during deserialization. + state.source_hash = raw_data.source_hash + state.apply_inline_configuration(raw_data.mypy_comments) + state.tree = load_from_raw( + state.xpath, + state.id, + raw_data, + self.errors, + state.options, + imports_only=bool(self.workers), + ) + if self.errors.is_blockers(): + self.log("Bailing due to parse errors") + self.errors.raise_error() - for state in parallel_parsed_states: - # New parser returns serialized ASTs. Deserialize full trees only if not using - # parallel workers. - with state.wrap_context(): + for state in parallel_states: assert state.tree is not None - raw_data = state.tree.raw_data - if raw_data is not None: - # Apply inline mypy config before deserialization, since - # some options (e.g. implicit_optional) affect deserialization - state.source_hash = raw_data.source_hash - state.apply_inline_configuration(raw_data.mypy_comments) - state.tree = load_from_raw( - state.xpath, - state.id, - raw_data, - self.errors, - state.options, - imports_only=bool(self.workers), - ) - if self.errors.is_blockers(): - self.log("Bailing due to parse errors") - self.errors.raise_error() - - for state in parallel_states: - assert state.tree is not None - if state in parallel_parsed_states_set: + if state in parallel_parsed_states_set: + if state.tree.raw_data is not None: + # source_hash was already extracted above, but raw_data + # may have been preserved for workers (imports_only=True). + pass + elif state.source_hash is None: + # At least namespace packages may not have source. + state.get_source() + state.early_errors = list(self.errors.error_info_map.get(state.xpath, [])) + state.semantic_analysis_pass1() + self.ast_cache[state.id] = (state.tree, state.early_errors, state.source_hash) + self.modules[state.id] = state.tree if state.tree.raw_data is not None: - # source_hash was already extracted above, but raw_data - # may have been preserved for workers (imports_only=True). - pass - elif state.source_hash is None: - # At least namespace packages may not have source. - state.get_source() - state.early_errors = list(self.errors.error_info_map.get(state.xpath, [])) - state.semantic_analysis_pass1() - self.ast_cache[state.id] = (state.tree, state.early_errors, state.source_hash) - self.modules[state.id] = state.tree - if state.tree.raw_data is not None: - state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT - state.check_blockers() - state.setup_errors() - - def parse_files_threaded_raw( - self, sequential_states: list[State], parallel_states: list[State] - ) -> tuple[list[State], set[State]]: - """Parse files using a thread pool. - - Also parse sequential states while waiting for the parallel results. + state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT + state.check_blockers() + state.setup_errors() + elif len(parallel_states) == 1: + # Avoid using executor when there is no parallelism. + parallel_states[0].parse_file() + + if post_parse: + self.post_parse_all(states) + + def parse_files_threaded_raw(self, states: list[State]) -> tuple[list[State], set[State]]: + """Parse files in parallel using a thread pool. + Trees from the new parser are left in raw (serialized) form. Return (list, set) of states that were actually parsed (not cached). @@ -1118,14 +1115,14 @@ def parse_files_threaded_raw( # parse_file_inner() results in no visible improvement with more than 8 threads. # TODO: reuse thread pool and/or batch small files in single submit() call. with ThreadPoolExecutor(max_workers=min(available_threads, 8)) as executor: - for state in parallel_states: + for state in states: state.needs_parse = False if state.id not in self.ast_cache: self.log(f"Parsing {state.xpath} ({state.id})") ignore_errors = state.ignore_all or state.options.ignore_errors if ignore_errors: self.errors.ignored_files.add(state.xpath) - futures.append(executor.submit(state.parse_file_inner, "")) + futures.append(executor.submit(state.parse_file_inner, state.source)) parallel_parsed_states.append(state) parallel_parsed_states_set.add(state) else: @@ -1133,10 +1130,6 @@ def parse_files_threaded_raw( state.tree, state.early_errors, source_hash = self.ast_cache[state.id] state.source_hash = source_hash - # Parse sequential before waiting on parallel. - for state in sequential_states: - state.parse_file() - for fut in wait(futures).done: fut.result() @@ -1279,7 +1272,7 @@ def parse_file( self, id: str, path: str, - source: str, + source: str | None, options: Options, raw_data: FileRawData | None = None, ) -> MypyFile: @@ -1287,13 +1280,12 @@ def parse_file( Raise CompileError if there is a parse error. """ - file_exists = self.fscache.exists(path, real_only=True) t0 = time.time() if raw_data: # If possible, deserialize from known binary data instead of parsing from scratch. tree = load_from_raw(path, id, raw_data, self.errors, options) else: - tree = parse(source, path, id, self.errors, options=options, file_exists=file_exists) + tree = parse(source, path, id, self.errors, options=options) tree._fullname = id if self.stats_enabled: with self.stats_lock: @@ -1971,23 +1963,29 @@ def get_cache_names(id: str, path: str, options: Options) -> tuple[str, str, str return prefix + meta_suffix, prefix + data_suffix, deps_json -def options_snapshot(id: str, manager: BuildManager) -> dict[str, object]: +def options_snapshot(module: str, manager: BuildManager) -> dict[str, object]: """Make compact snapshot of options for a module. Separately store only the options we may compare individually, and take a hash of everything else. If --debug-cache is specified, fall back to full snapshot. """ - platform_opt, values = manager.options.clone_for_module(id).select_options_affecting_cache() + cloned = manager.options.clone_for_module(module) if manager.options.debug_cache: # Build full options snapshot for debugging purposes. + platform_opt, values = cloned.select_options_affecting_cache() result: dict[str, object] = {"platform": platform_opt} for key, val in zip(OPTIONS_AFFECTING_CACHE_NO_PLATFORM, values): result[key] = val return result - # Process most options quickly, since this is performance critical. - buf = WriteBuffer() - write_json_value(buf, cast(JsonValue, values)) - return {"platform": platform_opt, "other_options": hash_digest(buf.getvalue())} + cache = manager.options_snapshot_cache + cached = cache.get(cloned) + if cached is None: + platform_opt, values = cloned.select_options_affecting_cache() + buf = WriteBuffer() + write_json_value(buf, cast(JsonValue, values)) + cached = (platform_opt, hash_digest(buf.getvalue())) + cache[cloned] = cached + return {"platform": cached[0], "other_options": cached[1]} def find_cache_meta( @@ -3179,7 +3177,7 @@ def get_source(self) -> str: else: err = f"{self.path}: error: Cannot decode file: {str(decodeerr)}" raise CompileError([err], module_with_blocker=self.id) from decodeerr - elif self.path and self.manager.fscache.isdir(self.path): + elif self.path and manager.fscache.isdir(self.path): source = "" self.source_hash = "" else: @@ -3192,7 +3190,7 @@ def get_source(self) -> str: self.time_spent_us += time_spent_us(t0) return source - def parse_file_inner(self, source: str, raw_data: FileRawData | None = None) -> None: + def parse_file_inner(self, source: str | None, raw_data: FileRawData | None = None) -> None: t0 = time_ref() self.tree = self.manager.parse_file( self.id, self.xpath, source, options=self.options, raw_data=raw_data @@ -3319,9 +3317,7 @@ def semantic_analysis_pass1(self) -> None: # # TODO: This should not be considered as a semantic analysis # pass -- it's an independent pass. - if not options.native_parser or not self.manager.fscache.exists( - self.xpath, real_only=True - ): + if not options.native_parser: analyzer = SemanticAnalyzerPreAnalysis() with self.wrap_context(): analyzer.visit_file(self.tree, self.xpath, self.id, options) @@ -4349,7 +4345,7 @@ def load_graph( for dep in st.ancestors + dependencies + st.suppressed: ignored = dep in st.suppressed_set and dep not in entry_points if ignored and dep not in added: - manager.missing_modules[dep] = SuppressionReason.NOT_FOUND + manager.missing_modules.setdefault(dep, SuppressionReason.NOT_FOUND) # TODO: for now we skip this in the daemon as a performance optimization. # This however creates a correctness issue, see #7777 and State.is_fresh(). if not manager.use_fine_grained_cache() or manager.options.warn_unused_configs: @@ -4635,10 +4631,11 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: ready = [] for done_scc in done: for dependent in done_scc.direct_dependents: - scc_by_id[dependent].not_ready_deps.discard(done_scc.id) - if not scc_by_id[dependent].not_ready_deps: - not_ready.remove(scc_by_id[dependent]) - ready.append(scc_by_id[dependent]) + dep_scc = scc_by_id[dependent] + dep_scc.not_ready_count -= 1 + if not dep_scc.not_ready_count: + not_ready.remove(dep_scc) + ready.append(dep_scc) manager.trace(f"Transitive deps cache size: {sys.getsizeof(manager.transitive_deps_cache)}") @@ -4769,13 +4766,13 @@ def process_stale_scc(graph: Graph, ascc: SCC, manager: BuildManager) -> None: t2 = time.time() stale = scc + # Parse before verify_dependencies so that inline config comments + # (e.g. "# mypy: disable-error-code") are applied to options. + manager.parse_all([graph[id] for id in stale], post_parse=False) for id in stale: # Re-generate import errors in case this module was loaded from the cache. if graph[id].meta: graph[id].verify_dependencies(suppressed_only=True) - # We may already have parsed the modules, or not. - # If the former, parse_file() is a no-op. - manager.parse_all([graph[id] for id in stale], post_parse=False) if "typing" in scc: # For historical reasons we need to manually add typing aliases # for built-in generic collections, see docstring of @@ -5019,9 +5016,10 @@ def prepare_sccs_full( for scc in sccs: # Remove trivial dependency on itself. scc_deps_map[scc].discard(scc) - for dep_scc in scc_deps_map[scc]: + dep_sccs = scc_deps_map[scc] + for dep_scc in dep_sccs: scc.deps.add(dep_scc.id) - scc.not_ready_deps.add(dep_scc.id) + scc.not_ready_count = len(dep_sccs) return scc_deps_map @@ -5091,18 +5089,33 @@ def deps_filtered(graph: Graph, vertices: AbstractSet[str], id: str, pri_max: in def transitive_dep_hash(scc: SCC, graph: Graph) -> bytes: """Compute stable snapshot of transitive import structure for given SCC.""" - all_direct_deps = sorted( - { - dep - for id in scc.mod_ids - for dep in graph[id].dependencies - if graph[id].priorities.get(dep) != PRI_INDIRECT - } - ) + mod_ids = scc.mod_ids + if len(mod_ids) == 1: + # Fast path: State.dependencies is already deduped and never contains + # self.id, so we can skip the dedupe set and the self-membership check. + (only_id,) = mod_ids + st = graph[only_id] + priorities = st.priorities + all_direct_deps = sorted( + dep for dep in st.dependencies if priorities.get(dep) != PRI_INDIRECT + ) + buf = WriteBuffer() + for dep_id in all_direct_deps: + write_str_bare(buf, dep_id) + write_bytes_bare(buf, graph[dep_id].trans_dep_hash) + return hash_digest_bytes(buf.getvalue()) + deps_set: set[str] = set() + for id in mod_ids: + state = graph[id] + priorities = state.priorities + for dep in state.dependencies: + if priorities.get(dep) != PRI_INDIRECT: + deps_set.add(dep) + all_direct_deps = sorted(deps_set) buf = WriteBuffer() for dep_id in all_direct_deps: write_str_bare(buf, dep_id) - if dep_id not in scc.mod_ids: + if dep_id not in mod_ids: write_bytes_bare(buf, graph[dep_id].trans_dep_hash) return hash_digest_bytes(buf.getvalue()) diff --git a/mypy/cache.py b/mypy/cache.py index b9cd8ad7a9050..ebe36e8940b81 100644 --- a/mypy/cache.py +++ b/mypy/cache.py @@ -69,7 +69,7 @@ from mypy_extensions import u8 # High-level cache layout format -CACHE_VERSION: Final = 8 +CACHE_VERSION: Final = 10 # Type used internally to represent errors: # (path, line, column, end_line, end_column, severity, message, code) @@ -478,7 +478,7 @@ def write_str_opt_list(data: WriteBuffer, value: list[str | None]) -> None: write_str_opt(data, item) -Value: _TypeAlias = None | int | str | bool +Value: _TypeAlias = None | int | float | str | bool # Our JSON format is somewhat non-standard as we distinguish lists and tuples. # This is convenient for some internal things, like mypyc plugin and error serialization. @@ -508,6 +508,8 @@ def read_json_value(data: ReadBuffer) -> JsonValue: if tag == DICT_STR_GEN: size = read_int_bare(data) return {read_str_bare(data): read_json_value(data) for _ in range(size)} + if tag == LITERAL_FLOAT: + return read_float_bare(data) assert False, f"Invalid JSON tag: {tag}" @@ -538,6 +540,9 @@ def write_json_value(data: WriteBuffer, value: JsonValue) -> None: for key in sorted(value): write_str_bare(data, key) write_json_value(data, value[key]) + elif isinstance(value, float): + write_tag(data, LITERAL_FLOAT) + write_float_bare(data, value) else: assert False, f"Invalid JSON value: {value}" @@ -558,6 +563,20 @@ def write_json(data: WriteBuffer, value: dict[str, Any]) -> None: write_json_value(data, value[key]) +def write_flags(data: WriteBuffer, flags: list[bool]) -> None: + assert len(flags) <= 26, "This many flags not supported yet" + packed = 0 + for i, flag in enumerate(flags): + if flag: + packed |= 1 << i + write_int(data, packed) + + +def read_flags(data: ReadBuffer, num_flags: int) -> list[bool]: + packed = read_int(data) + return [(packed & (1 << i)) != 0 for i in range(num_flags)] + + def write_errors(data: WriteBuffer, errs: list[ErrorTuple]) -> None: write_tag(data, LIST_GEN) write_int_bare(data, len(errs)) diff --git a/mypy/checker.py b/mypy/checker.py index 2d23f9c133164..612d2face5b8a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1825,6 +1825,7 @@ def check_unbound_return_typevar(self, typ: CallableType) -> None: "Consider using the upper bound " f"{format_type(typ.ret_type.upper_bound, self.options)} instead", context=typ.ret_type, + code=TYPE_VAR, ) def check_default_params(self, item: FuncItem, body_is_trivial: bool | None = None) -> None: @@ -2813,6 +2814,8 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_multiple_inheritance(typ) self.check_metaclass_compatibility(typ) self.check_final_deletable(typ) + if typ.typeddict_type: + self.check_typeddict_inheritance(defn) if defn.decorators: sig: Type = type_object_type(defn.info) @@ -3130,7 +3133,7 @@ class C(B, A[int]): ... # this is unsafe because... x: A[int] = C() x.foo # ...runtime type is (str) -> None, while static type is (int) -> None """ - if name in ("__init__", "__new__", "__init_subclass__"): + if name in {"__init__", "__new__", "__init_subclass__", "__replace__"}: # __init__ and friends can be incompatible -- it's a special case. return first = base1.names[name] @@ -3219,6 +3222,44 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: if explanation: self.note(explanation, typ, code=codes.METACLASS) + def check_typeddict_inheritance(self, defn: ClassDef) -> None: + """Ensure that the final definition of a TypedDict is compatible with its base classes.""" + assert defn.info.typeddict_type + td = defn.info.typeddict_type + data = defn.info.typeddict_data + if data is None or not data.ready: + return + for base, base_items in data.bases: + assert base.typeddict_type + for field_name, base_type in base_items.items(): + field_type = td.items[field_name] + assert field_type + is_readonly = field_name in base.typeddict_type.readonly_keys + if is_readonly: + is_compatible = is_subtype(field_type, base_type) + else: + is_compatible = is_equivalent(field_type, base_type) + if not is_compatible: + source = data.field_sources[field_name] + if source.base is None: + self.fail( + f'Definition of field "{field_name}" incompatible with base class ' + f'"{base.name}"', + source.ctx, + ) + else: + self.fail( + f'Incompatible definitions of field "{field_name}" in base classes ' + f'"{base.name}" and "{source.base.name}"', + source.ctx, + ) + if field_name in td.readonly_keys: + self.note( + f'This can be resolved by redeclaring the field "{field_name}" ' + f"with a mutually compatible type", + source.ctx, + ) + def visit_import_from(self, node: ImportFrom) -> None: for name, _ in node.names: if (sym := self.globals.get(name)) is not None: @@ -4552,7 +4593,7 @@ def check_lvalue( self.check_lvalue(sub_expr)[0] or # This type will be used as a context for further inference of rvalue, # we put Uninhabited if there is no information available from lvalue. - UninhabitedType() + UninhabitedType(ambiguous=True) for sub_expr in lvalue.items ] lvalue_type = TupleType(types, self.named_type("builtins.tuple")) @@ -5515,7 +5556,7 @@ def check_except_handler_test(self, n: Expression, is_star: bool) -> Type: if not item.is_type_obj(): self.fail(message_registry.INVALID_EXCEPTION_TYPE, n) return self.default_exception_type(is_star) - exc_type = erase_typevars(item.ret_type) + exc_type = erase_typevars(item.get_instance_type()) elif isinstance(ttype, TypeType): exc_type = ttype.item else: @@ -6393,7 +6434,8 @@ def conditional_types_for_iterable( ) -> tuple[Type, Type]: """ Narrows the type of `iterable_type` based on the type of `item_type`. - For now, we only support narrowing unions of TypedDicts based on left operand being literal string(s). + For now, we only support narrowing unions of TypedDicts, and TypeVars with TypedDict + bounds, based on left operand being literal string(s). """ if_types: list[Type] = [] else_types: list[Type] = [] @@ -6407,16 +6449,30 @@ def conditional_types_for_iterable( item_str_literals = try_getting_str_literals_from_type(item_type) for possible_iterable_type in possible_iterable_types: - if item_str_literals and isinstance(possible_iterable_type, TypedDictType): + bound = ( + get_proper_type(possible_iterable_type.upper_bound) + if isinstance(possible_iterable_type, TypeVarType) + else possible_iterable_type + ) + + if item_str_literals and isinstance(bound, TypedDictType): for key in item_str_literals: - if key in possible_iterable_type.required_keys: + if key in bound.required_keys: if_types.append(possible_iterable_type) - elif ( - key in possible_iterable_type.items or not possible_iterable_type.is_final + elif key in bound.items and isinstance( + get_proper_type(bound.items[key]), UninhabitedType ): - if_types.append(possible_iterable_type) + # If an item is explicitly declared uninhabited, we can exclude it from + # if_types; see testOperatorContainsNarrowsTypedDicts_closed + else_types.append(possible_iterable_type) + elif key not in bound.items and (bound.is_closed or bound.is_final): + # If an item is missing and the type is closed, we can exclude it from + # if_types; see testOperatorContainsNarrowsTypedDicts_closed + # We also support "final" as a legacy way of expressing "closed" in this + # specific case; see testOperatorContainsNarrowsTypedDicts_final else_types.append(possible_iterable_type) else: + if_types.append(possible_iterable_type) else_types.append(possible_iterable_type) else: if_types.append(possible_iterable_type) @@ -6771,28 +6827,73 @@ def comparison_type_narrowing_helper(self, node: ComparisonExpr) -> tuple[TypeMa else_map = {} if left_index in narrowable_operand_index_to_hash: - collection_item_type = get_proper_type(builtin_item_type(iterable_type)) - if collection_item_type is not None: - if_map, else_map = self.narrow_type_by_identity_equality( - "==", - operands=[operands[left_index], operands[right_index]], - operand_types=[item_type, collection_item_type], - expr_indices=[0, 1], - narrowable_indices={0}, - ) - if else_map and not ( - isinstance(p_typ := get_proper_type(iterable_type), TupleType) - and all( - is_singleton_equality_type(get_proper_type(item)) - for item in p_typ.items + p_iterable_type = get_proper_type(iterable_type) + container_item_types = None + + # Can we statically determine container contents? + if ( + isinstance(p_iterable_type, TupleType) + and find_unpack_in_list(p_iterable_type.items) is None + ): + container_item_types = p_iterable_type.items + else: + container_expr = collapse_walrus(operands[right_index]) + if isinstance(container_expr, (ListExpr, SetExpr)): + if all(not isinstance(i, StarExpr) for i in container_expr.items): + container_item_types = [ + self.lookup_type(e) for e in container_expr.items + ] + elif isinstance(container_expr, DictExpr): + if all(k is not None for k, v in container_expr.items): + container_item_types = [ + self.lookup_type(cast(Expression, k)) + for k, v in container_expr.items + ] + + if container_item_types is not None: + # If we know the exact contents, we can potentially do negative narrowing, + # e.g. `x not in (None,)` + all_if_maps = [] + all_else_maps = [] + for known_item in container_item_types: + # Match the should_coerce_literals logic from narrow_type_by_identity_equality + p_known_item = get_proper_type(known_item) + if ( + is_literal_type_like(p_known_item) + or ( + isinstance(p_known_item, Instance) + and p_known_item.type.is_enum + ) + ) and not has_custom_eq_checks(p_known_item): + known_item = coerce_to_literal(known_item) + if_map, else_map = self.narrow_type_by_identity_equality( + "==", + operands=[operands[left_index], operands[right_index]], + operand_types=[item_type, known_item], + expr_indices=[0, 1], + narrowable_indices={0}, ) - ): - # In general, we can't do negative narrowing, since e.g. the container - # could just be empty. However, we can do negative narrowing for some - # tuples e.g. `x not in (None,)` + all_if_maps.append(if_map) + if is_singleton_equality_type(get_proper_type(known_item)): + all_else_maps.append(else_map) + if_map = reduce_or_conditional_type_maps(all_if_maps) + else_map = reduce_and_conditional_type_maps(all_else_maps, use_meet=True) + else: + collection_item_type = get_proper_type(builtin_item_type(p_iterable_type)) + if collection_item_type is not None: + if_map, else_map = self.narrow_type_by_identity_equality( + "==", + operands=[operands[left_index], operands[right_index]], + operand_types=[item_type, collection_item_type], + expr_indices=[0, 1], + narrowable_indices={0}, + ) + # We can't do negative narrowing, since e.g. the container could + # just be empty. else_map = {} if right_index in narrowable_operand_index_to_hash: + # E.g. narrows the right operand in `if "key" in typed_dict` if_type, else_type = self.conditional_types_for_iterable( item_type, iterable_type ) @@ -8032,7 +8133,20 @@ def iterable_item_type(self, it: ProperType, context: Context) -> Type: return self.analyze_iterable_item_type_without_expression(it, context)[1] def function_type(self, func: FuncBase) -> FunctionLike: - return function_type(func, self.named_type("builtins.function")) + typ = function_type(func, self.named_type("builtins.function")) + if ( + isinstance(func, FuncItem) + and func.is_coroutine + and not func.is_async_generator + and func.type is None + and isinstance(typ, CallableType) + ): + any_type = AnyType(TypeOfAny.special_form) + ret_type = self.named_generic_type( + "typing.Coroutine", [any_type, any_type, typ.ret_type] + ) + return typ.copy_modified(ret_type=ret_type) + return typ def push_type_map(self, type_map: TypeMap, *, from_assignment: bool = True) -> None: if is_unreachable_map(type_map): @@ -8488,6 +8602,9 @@ def lookup_fully_qualified_or_none(self, fullname: str, /) -> SymbolTableNode | except KeyError: return None + def record_fixed_type(self, fixed: TypeInfo | TypeAlias) -> None: + pass + def fail( self, msg: str, @@ -8536,6 +8653,9 @@ def is_func_scope(self) -> bool: # message types are ignored. return False + def is_nested_within_func_scope(self) -> bool: + return self._chk.scope.top_level_function() is not None + @property def type(self) -> TypeInfo | None: return self._chk.scope.current_class() @@ -8772,6 +8892,7 @@ def builtin_item_type(tp: Type) -> Type | None: "builtins.list", "builtins.tuple", "builtins.dict", + "builtins.frozendict", "builtins.set", "builtins.frozenset", "_collections_abc.dict_keys", diff --git a/mypy/checker_shared.py b/mypy/checker_shared.py index 2b25d3e73a6fe..c1c4cee748b32 100644 --- a/mypy/checker_shared.py +++ b/mypy/checker_shared.py @@ -16,6 +16,7 @@ ArgKind, Context, Expression, + FuncBase, FuncItem, LambdaExpr, MypyFile, @@ -28,6 +29,7 @@ from mypy.plugin import CheckerPluginInterface, Plugin from mypy.types import ( CallableType, + FunctionLike, Instance, LiteralValue, Overloaded, @@ -149,6 +151,10 @@ def expr_checker(self) -> ExpressionCheckerSharedApi: def named_type(self, name: str) -> Instance: raise NotImplementedError + @abstractmethod + def function_type(self, func: FuncBase) -> FunctionLike: + raise NotImplementedError + @abstractmethod def lookup_typeinfo(self, fullname: str) -> TypeInfo: raise NotImplementedError diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 123c5f821ed29..172d44555b946 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -124,7 +124,6 @@ from mypy.subtypes import ( covers_at_runtime, find_member, - is_equivalent, is_same_type, is_subtype, non_method_protocol_members, @@ -151,7 +150,6 @@ false_only, fixup_partial_type, freeze_all_type_vars, - function_type, get_all_type_vars, get_type_vars, is_literal_type_like, @@ -416,7 +414,7 @@ def analyze_static_reference( if isinstance(node, (Var, Decorator, OverloadedFuncDef)): return node.type or AnyType(TypeOfAny.special_form) elif isinstance(node, FuncDef): - return function_type(node, self.named_type("builtins.function")) + return self.chk.function_type(node) elif isinstance(node, TypeInfo): # Reference to a type object. if node.typeddict_type: @@ -689,7 +687,7 @@ def method_fullname(self, object_type: Type, method_name: str) -> str | None: # For class method calls, object_type is a callable representing the class object. # We "unwrap" it to a regular type, as the class/instance method difference doesn't # affect the fully qualified name. - object_type = get_proper_type(object_type.ret_type) + object_type = object_type.get_instance_type() elif isinstance(object_type, TypeType): object_type = object_type.item @@ -717,9 +715,9 @@ def always_returns_none(self, node: Expression) -> bool: if isinstance(typ, Instance): info = typ.type elif isinstance(typ, CallableType) and typ.is_type_obj(): - ret_type = get_proper_type(typ.ret_type) - if isinstance(ret_type, Instance): - info = ret_type.type + instance_type = typ.get_instance_type(force_fallback=True) + if isinstance(instance_type, Instance): + info = instance_type.type else: return False else: @@ -819,8 +817,8 @@ def validate_typeddict_kwargs( result = defaultdict(list) # Keys that are guaranteed to be present no matter what (e.g. for all items of a union) always_present_keys = set() - # Indicates latest encountered ** unpack among items. - last_star_found = None + # Indicates latest encountered ** unpack of a non-closed type among items. + last_open_star_found = None for item_name_expr, item_arg in kwargs: if item_name_expr: @@ -844,22 +842,30 @@ def validate_typeddict_kwargs( result[literal_value] = [item_arg] always_present_keys.add(literal_value) else: - last_star_found = item_arg - if not self.validate_star_typeddict_item( + is_valid, is_open = self.validate_star_typeddict_item( item_arg, callee, result, always_present_keys - ): + ) + if not is_valid: return None - if self.chk.options.extra_checks and last_star_found is not None: + if is_open: + last_open_star_found = item_arg + if self.chk.options.extra_checks and last_open_star_found is not None: + if callee.is_closed: + self.chk.fail( + "Cannot unpack item that may contain extra keys into a closed TypedDict", + last_open_star_found, + code=codes.TYPEDDICT_ITEM, + ) absent_keys = [] for key in callee.items: if key not in callee.required_keys and key not in result: absent_keys.append(key) if absent_keys: - # Having an optional key not explicitly declared by a ** unpacked + # Having an optional key not explicitly declared by a ** unpacked open # TypedDict is unsafe, it may be an (incompatible) subtype at runtime. # TODO: catch the cases where a declared key is overridden by a subsequent # ** item without it (and not again overridden with complete ** item). - self.msg.non_required_keys_absent_with_star(absent_keys, last_star_found) + self.msg.non_required_keys_absent_with_star(absent_keys, last_open_star_found) return result, always_present_keys def validate_star_typeddict_item( @@ -868,14 +874,18 @@ def validate_star_typeddict_item( callee: TypedDictType, result: dict[str, list[Expression]], always_present_keys: set[str], - ) -> bool: + ) -> tuple[bool, bool]: """Update keys/expressions from a ** expression in TypedDict constructor. - Note `result` and `always_present_keys` are updated in place. Return true if the - expression `item_arg` may valid in `callee` TypedDict context. + Note `result` and `always_present_keys` are updated in place. + + First tuple item returned is true if the expression `item_arg` may valid + in `callee` TypedDict context. Second tuple item returned is true if the + expression may contain other keys not explicitly declared. """ inferred = get_proper_type(self.accept(item_arg, type_context=callee)) - possible_tds = [] + any_fallback = False + possible_tds: list[TypedDictType] = [] if isinstance(inferred, TypedDictType): possible_tds = [inferred] elif isinstance(inferred, UnionType): @@ -884,10 +894,14 @@ def validate_star_typeddict_item( possible_tds.append(item) elif not self.valid_unpack_fallback_item(item): self.msg.unsupported_target_for_star_typeddict(item, item_arg) - return False + return False, True + else: + any_fallback = True elif not self.valid_unpack_fallback_item(inferred): self.msg.unsupported_target_for_star_typeddict(inferred, item_arg) - return False + return False, True + else: + any_fallback = True all_keys: set[str] = set() for td in possible_tds: all_keys |= td.items.keys() @@ -916,7 +930,8 @@ def validate_star_typeddict_item( # If this key is not required at least in some item of a union # it may not shadow previous item, so we need to type check both. result[key].append(arg) - return True + all_closed = all(t.is_closed for t in possible_tds) + return True, any_fallback or not all_closed def valid_unpack_fallback_item(self, typ: ProperType) -> bool: if isinstance(typ, AnyType): @@ -932,7 +947,8 @@ def match_typeddict_call_with_dict( kwargs: list[tuple[Expression | None, Expression]], context: Context, ) -> bool: - result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee) + with self.msg.filter_errors(): + result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee) if result is not None: validated_kwargs, _ = result return callee.required_keys <= set(validated_kwargs.keys()) <= set(callee.items.keys()) @@ -1667,9 +1683,10 @@ def check_callable_call( callee = callee.with_unpacked_kwargs().with_normalized_var_args() if callable_name is None and callee.name: callable_name = callee.name - ret_type = get_proper_type(callee.ret_type) - if callee.is_type_obj() and isinstance(ret_type, Instance): - callable_name = ret_type.type.fullname + if callee.is_type_obj(): + instance_type = callee.get_instance_type(force_fallback=True) + if isinstance(instance_type, Instance): + callable_name = instance_type.type.fullname if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). return callee.ret_type, callee @@ -1747,6 +1764,28 @@ def check_callable_call( lambda i: self.accept(args[i]), ) + if callee.special_sig == "tuple" and len(args) == 1: + with self.msg.filter_errors(): + arg_type = get_proper_type(self.accept(args[0])) + # Give precise constructor signature for situations like this: + # class Shape[*Ts](tuple[*Ts]): ... + # Shape((1, 2)) + # The argument type is the same as return type, but with builtins.tuple fallback. + if isinstance(arg_type, TupleType): + assert isinstance(callee.ret_type, ProperType) + if isinstance(callee.ret_type, TupleType): + # Actual type argument is ignored by tuple_fallback() in this case. + any_type = AnyType(TypeOfAny.special_form) + new_arg_type = callee.ret_type.copy_modified( + fallback=self.chk.named_generic_type("builtins.tuple", [any_type]) + ) + callee = callee.copy_modified(arg_types=[new_arg_type]) + elif isinstance(callee.ret_type, Instance): + new_arg_type = map_instance_to_supertype( + callee.ret_type, self.chk.lookup_typeinfo("builtins.tuple") + ) + callee = callee.copy_modified(arg_types=[new_arg_type]) + if callee.is_generic(): need_refresh = any( isinstance(v, (ParamSpecType, TypeVarTupleType)) for v in callee.variables @@ -1787,6 +1826,7 @@ def check_callable_call( might_have_shifted_args = ( not self.msg.prefer_simple_messages() + and len(args) >= 2 # see gh-21427 and all(k == ARG_POS for k in callee.arg_kinds) and all(k == ARG_POS for k in arg_kinds) and len(arg_kinds) == len(callee.arg_kinds) - 1 @@ -1843,7 +1883,7 @@ def check_callable_call( if ( callee.is_type_obj() and (len(arg_types) == 1) - and is_equivalent(callee.ret_type, self.named_type("builtins.type")) + and is_named_instance(callee.get_instance_type(), "builtins.type") ): callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) @@ -1924,12 +1964,7 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: # but better than AnyType...), but replace the return type # with typevar. callee = self.analyze_type_type_callee(get_proper_type(item.upper_bound), context) - callee = get_proper_type(callee) - if isinstance(callee, CallableType): - callee = callee.copy_modified(ret_type=item) - elif isinstance(callee, Overloaded): - callee = Overloaded([c.copy_modified(ret_type=item) for c in callee.items]) - return callee + return self.replace_type_type_callee_ret_type(callee, item) # We support Type of namedtuples but not of tuples in general if isinstance(item, TupleType) and tuple_fallback(item).type.fullname != "builtins.tuple": return self.analyze_type_type_callee(tuple_fallback(item), context) @@ -1939,6 +1974,23 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: self.msg.unsupported_type_type(item, context) return AnyType(TypeOfAny.from_error) + def replace_type_type_callee_ret_type(self, callee: Type, ret_type: Type) -> Type: + callee = get_proper_type(callee) + if isinstance(callee, CallableType): + return callee.copy_modified(ret_type=ret_type) + if isinstance(callee, Overloaded): + return Overloaded([c.copy_modified(ret_type=ret_type) for c in callee.items]) + if isinstance(callee, UnionType): + return UnionType( + [ + self.replace_type_type_callee_ret_type(item, ret_type) + for item in callee.relevant_items() + ], + line=callee.line, + column=callee.column, + ) + return callee + def infer_arg_types_in_empty_context(self, args: list[Expression]) -> list[Type]: """Infer argument expression types in an empty context. @@ -2084,7 +2136,7 @@ def infer_function_type_arguments_using_context( # Only substitute non-Uninhabited and non-erased types. new_args: list[Type | None] = [] for arg in args: - if has_uninhabited_component(arg) or has_erased_component(arg): + if has_ambiguous_uninhabited_component(arg) or has_erased_component(arg): new_args.append(None) else: new_args.append(arg) @@ -3242,6 +3294,7 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call assert types, "Trying to merge no callables" if not all(isinstance(c, CallableType) for c in types): return AnyType(TypeOfAny.special_form) + callables = cast("list[CallableType]", types) if len(callables) == 1: return callables[0] @@ -3259,11 +3312,11 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call # confusing and ought to be re-written anyways.) callables, variables = merge_typevars_in_callables_by_name(callables) - new_args: list[list[Type]] = [[] for _ in range(len(callables[0].arg_types))] + new_args: list[list[Type]] = [[] for _ in callables[0].arg_types] new_kinds = list(callables[0].arg_kinds) new_returns: list[Type] = [] - too_complex = False + for target in callables: # We fall back to Callable[..., Union[]] if the functions do not have # the exact same signature. The only exception is if one arg is optional and @@ -3276,15 +3329,13 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call for i, (new_kind, target_kind) in enumerate(zip(new_kinds, target.arg_kinds)): if new_kind == target_kind: continue - elif new_kind.is_positional() and target_kind.is_positional(): + if new_kind.is_positional() and target_kind.is_positional(): new_kinds[i] = ARG_POS else: too_complex = True break - if too_complex: - break # outer loop - + break for i, arg in enumerate(target.arg_types): new_args[i].append(arg) new_returns.append(target.ret_type) @@ -3301,13 +3352,8 @@ def combine_function_signatures(self, types: list[ProperType]) -> AnyType | Call implicit=True, ) - final_args = [] - for args_list in new_args: - new_type = make_simplified_union(args_list) - final_args.append(new_type) - return callables[0].copy_modified( - arg_types=final_args, + arg_types=[make_simplified_union(args) for args in new_args], arg_kinds=new_kinds, ret_type=union_return, variables=variables, @@ -4920,10 +4966,11 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: if tapp.expr.node.python_3_12_type_alias: return self.type_alias_type_type() # Subscription of a (generic) alias in runtime context, expand the alias. - item = instantiate_type_alias( + item, _ = instantiate_type_alias( tapp.expr.node, tapp.types, self.chk.fail, + self.chk.note, tapp.expr.node.no_args, tapp, self.chk.options, @@ -4988,17 +5035,16 @@ class LongName(Generic[T]): ... # A = List[Tuple[T, T]] # x = A() <- same as List[Tuple[Any, Any]], see PEP 484. disallow_any = self.chk.options.disallow_any_generics and self.is_callee - item = get_proper_type( - set_any_tvars( - alias, - [], - ctx.line, - ctx.column, - self.chk.options, - disallow_any=disallow_any, - fail=self.msg.fail, - ) + item, _ = set_any_tvars( + alias, + [], + ctx.line, + ctx.column, + self.chk.options, + disallow_any=disallow_any, + fail=self.msg.fail, ) + item = get_proper_type(item) if isinstance(item, Instance): # Normally we get a callable type (or overloaded) with .is_type_obj() true # representing the class's constructor @@ -5061,11 +5107,7 @@ class C(Generic[T, Unpack[Ts]]): ... return [AnyType(TypeOfAny.from_error)] * len(vars) # TODO: in future we may want to support type application to variadic functions. - if ( - not vars - or not any(isinstance(v, TypeVarTupleType) for v in vars) - or not t.is_type_obj() - ): + if not vars or not t.is_type_obj() or t.type_object().fullname == "builtins.tuple": return list(args) info = t.type_object() # We reuse the logic from semanal phase to reduce code duplication. @@ -5079,6 +5121,9 @@ class C(Generic[T, Unpack[Ts]]): ... ) args = list(fake.args) + if not any(isinstance(v, TypeVarTupleType) for v in vars): + return args + prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType)) suffix = len(vars) - prefix - 1 tvt = vars[prefix] @@ -6724,8 +6769,8 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> bool: return True -def has_ambiguous_uninhabited_component(t: Type) -> bool: - return t.accept(HasAmbiguousUninhabitedComponentsQuery()) +def has_ambiguous_uninhabited_component(t: Type | None) -> bool: + return t is not None and t.accept(HasAmbiguousUninhabitedComponentsQuery()) class HasAmbiguousUninhabitedComponentsQuery(types.BoolTypeQuery): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b5dcf94a0b206..3ba99d8e8c6b2 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -47,7 +47,6 @@ bind_self, erase_to_bound, freeze_all_type_vars, - function_type, get_all_type_vars, make_simplified_union, supported_self_type, @@ -360,7 +359,7 @@ def analyze_instance_member_access( if mx.is_lvalue and not mx.suppress_errors: mx.msg.cant_assign_to_method(mx.context) if not isinstance(method, OverloadedFuncDef): - signature = function_type(method, mx.named_type("builtins.function")) + signature = mx.chk.function_type(method) else: if method.type is None: # Overloads may be not ready if they are decorated. Handle this in same @@ -407,15 +406,8 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None: def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: MemberContext) -> Type: # Class attribute. # TODO super? - ret_type = typ.items[0].ret_type - assert isinstance(ret_type, ProperType) - if isinstance(ret_type, TupleType): - ret_type = tuple_fallback(ret_type) - if isinstance(ret_type, TypedDictType): - ret_type = ret_type.fallback - if isinstance(ret_type, LiteralType): - ret_type = ret_type.fallback - if isinstance(ret_type, Instance): + instance_type = typ.items[0].get_instance_type(force_fallback=True) + if isinstance(instance_type, Instance): if not mx.is_operator: # When Python sees an operator (eg `3 == 4`), it automatically translates that # into something like `int.__eq__(3, 4)` instead of `(3).__eq__(4)` as an @@ -432,14 +424,18 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member # See https://github.com/python/mypy/pull/1787 for more info. # TODO: do not rely on same type variables being present in all constructor overloads. result = analyze_class_attribute_access( - ret_type, name, mx, original_vars=typ.items[0].variables, mcs_fallback=typ.fallback + instance_type, + name, + mx, + original_vars=typ.items[0].variables, + mcs_fallback=typ.fallback, ) if result: return result # Look up from the 'type' type. return _analyze_member_access(name, typ.fallback, mx) else: - assert False, f"Unexpected type {ret_type!r}" + assert False, f"Unexpected type {instance_type!r}" def analyze_type_type_member_access( @@ -721,7 +717,7 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: dunder_get_type = expand_type_by_instance(bound_method, typ) if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): - owner_type = instance_type.items[0].ret_type + owner_type = instance_type.items[0].get_instance_type() instance_type = NoneType() elif isinstance(instance_type, TypeType): owner_type = instance_type.item @@ -1328,7 +1324,7 @@ def analyze_class_attribute_access( return AnyType(TypeOfAny.from_error) else: assert isinstance(node.node, SYMBOL_FUNCBASE_TYPES) - typ = function_type(node.node, mx.named_type("builtins.function")) + typ = mx.chk.function_type(node.node) # Note: if we are accessing class method on class object, the cls argument is bound. # Annotated and/or explicit class methods go through other code paths above, for # unannotated implicit class methods we do this here. @@ -1493,7 +1489,7 @@ def analyze_decorator_or_funcbase_access( """ if isinstance(defn, Decorator): return analyze_var(name, defn.var, itype, mx) - typ = function_type(defn, mx.chk.named_type("builtins.function")) + typ = mx.chk.function_type(defn) if isinstance(defn, (FuncDef, OverloadedFuncDef)) and defn.is_trivial_self: return bind_self_fast(typ, mx.self_type) typ = check_self_arg(typ, mx.self_type, defn.is_class, mx.context, name, mx.msg) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index e96af007e29c9..aba49d71b77ec 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -587,7 +587,6 @@ def apply_field_accessors( module=None, options=self.chk.options, errors=temp_errors, - file_exists=False, eager=True, ) if temp_errors.is_errors(): diff --git a/mypy/constraints.py b/mypy/constraints.py index df79fdae5456c..48cc23f742227 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -9,6 +9,7 @@ import mypy.typeops from mypy.argmap import ArgTypeExpander from mypy.erasetype import erase_typevars +from mypy.expandtype import expand_type_by_instance from mypy.maptype import map_instance_to_supertype from mypy.nodes import ( ARG_OPT, @@ -275,7 +276,11 @@ def infer_constraints_for_callable( def infer_constraints( - template: Type, actual: Type, direction: int, skip_neg_op: bool = False + template: Type, + actual: Type, + direction: int, + skip_neg_op: bool = False, + erase_types: bool = True, ) -> list[Constraint]: """Infer type constraints. @@ -312,14 +317,14 @@ def infer_constraints( # Return early on an empty branch. return [] type_state.inferring.append((template, actual)) - res = _infer_constraints(template, actual, direction, skip_neg_op) + res = _infer_constraints(template, actual, direction, skip_neg_op, erase_types) type_state.inferring.pop() return res - return _infer_constraints(template, actual, direction, skip_neg_op) + return _infer_constraints(template, actual, direction, skip_neg_op, erase_types) def _infer_constraints( - template: Type, actual: Type, direction: int, skip_neg_op: bool + template: Type, actual: Type, direction: int, skip_neg_op: bool, erase_types: bool ) -> list[Constraint]: orig_template = template template = get_proper_type(template) @@ -424,7 +429,7 @@ def _infer_constraints( return [] # Remaining cases are handled by ConstraintBuilderVisitor. - return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op)) + return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op, erase_types)) def _is_type_type(tp: ProperType) -> TypeGuard[TypeType | UnionType]: @@ -659,7 +664,9 @@ class ConstraintBuilderVisitor(TypeVisitor[list[Constraint]]): # TODO: The value may be None. Is that actually correct? actual: ProperType - def __init__(self, actual: ProperType, direction: int, skip_neg_op: bool) -> None: + def __init__( + self, actual: ProperType, direction: int, skip_neg_op: bool, erase_types: bool + ) -> None: # Direction must be SUBTYPE_OF or SUPERTYPE_OF. self.actual = actual self.direction = direction @@ -667,6 +674,10 @@ def __init__(self, actual: ProperType, direction: int, skip_neg_op: bool) -> Non # this is used to prevent infinite recursion when both template and actual are # generic callables. self.skip_neg_op = skip_neg_op + # Normally we should erase generic actual type when inferring against type[T] + # to avoid leaking type variables, see testGenericClassAsArgumentToType. + # The only exception is self-types in generic classes, where we set this to False. + self.erase_types = erase_types # Trivial leaf types @@ -759,13 +770,11 @@ def visit_instance(self, template: Instance) -> list[Constraint]: and template.type.is_protocol and self.direction == SUPERTYPE_OF ): - ret_type = get_proper_type(actual.ret_type) - if isinstance(ret_type, TupleType): - ret_type = mypy.typeops.tuple_fallback(ret_type) - if isinstance(ret_type, Instance): + instance_type = actual.get_instance_type(force_fallback=True) + if isinstance(instance_type, Instance): res.extend( self.infer_constraints_from_protocol_members( - ret_type, template, ret_type, template, class_obj=True + instance_type, template, instance_type, template, class_obj=True ) ) actual = actual.fallback @@ -797,7 +806,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: if isinstance(actual, Instance): instance = actual erased = erase_typevars(template) - assert isinstance(erased, Instance) # type: ignore[misc] + assert isinstance(erased, ProperType) and isinstance(erased, Instance) # We always try nominal inference if possible, # it is much faster than the structural one. if self.direction == SUBTYPE_OF and template.type.has_base(instance.type.fullname): @@ -996,7 +1005,24 @@ def visit_instance(self, template: Instance) -> list[Constraint]: res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: - return infer_constraints(template, mypy.typeops.tuple_fallback(actual), self.direction) + instance = mypy.typeops.tuple_fallback(actual) + erased = erase_typevars(template) + assert isinstance(erased, ProperType) and isinstance(erased, Instance) + # Special-case protocols before using fallback to get more precise constraints + # for custom tuple types like NamedTuples. + if ( + template.type.is_protocol + and self.direction == SUPERTYPE_OF + and not any(template == t for t in reversed(template.type.inferring)) + and mypy.subtypes.is_protocol_implementation(instance, erased, skip=["__call__"]) + ): + template.type.inferring.append(template) + res = self.infer_constraints_from_protocol_members( + instance, template, original_actual, template + ) + template.type.inferring.pop() + return res + return infer_constraints(template, instance, self.direction) elif isinstance(actual, TypeVarType): if not actual.values and not actual.id.is_meta_var(): return infer_constraints(template, actual.upper_bound, self.direction) @@ -1196,6 +1222,20 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: elif isinstance(self.actual, Overloaded): return self.infer_against_overloaded(self.actual, template) elif isinstance(self.actual, TypeType): + # This matches the corresponding logic in subtypes.py. + item = self.actual.item + if isinstance(item, TupleType): + item = mypy.typeops.tuple_fallback(item) + if isinstance(item, Instance): + constructor = mypy.typeops.type_object_type(item.type) + constructor = expand_type_by_instance(constructor, item) + # Only consider return type to match historic behavior (see below). + if isinstance(constructor, CallableType): + return infer_constraints( + template.ret_type, constructor.ret_type, self.direction + ) + elif isinstance(constructor, Overloaded): + return self.infer_against_overloaded(constructor, template, ret_only=True) return infer_constraints(template.ret_type, self.actual.item, self.direction) elif isinstance(self.actual, Instance): # Instances with __call__ method defined are considered structural @@ -1211,7 +1251,7 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]: return [] def infer_against_overloaded( - self, overloaded: Overloaded, template: CallableType + self, overloaded: Overloaded, template: CallableType, ret_only: bool = False ) -> list[Constraint]: # Create constraints by matching an overloaded type against a template. # This is tricky to do in general. We cheat by only matching against @@ -1219,13 +1259,16 @@ def infer_against_overloaded( # seems to work somewhat well, but we should really use a more # reliable technique. item = find_matching_overload_item(overloaded, template) + if ret_only: + return infer_constraints(template.ret_type, item.ret_type, self.direction) return infer_constraints(template, item, self.direction) def visit_tuple_type(self, template: TupleType) -> list[Constraint]: actual = self.actual unpack_index = find_unpack_in_list(template.items) - is_varlength_tuple = ( - isinstance(actual, Instance) and actual.type.fullname == "builtins.tuple" + # TODO: we may need to be careful with direction to avoid spurious constraints. + is_varlength_tuple = isinstance(actual, Instance) and actual.type.has_base( + "builtins.tuple" ) if isinstance(actual, TupleType) or is_varlength_tuple: @@ -1237,23 +1280,26 @@ def visit_tuple_type(self, template: TupleType) -> list[Constraint]: unpack_type = template.items[unpack_index] assert isinstance(unpack_type, UnpackType) unpacked_type = get_proper_type(unpack_type.type) + assert isinstance(actual, Instance) # ensured by is_varlength_tuple == True + mapped = map_instance_to_supertype( + actual, actual.type.get_base("builtins.tuple") + ) if isinstance(unpacked_type, TypeVarTupleType): res = [ - Constraint(type_var=unpacked_type, op=self.direction, target=actual) + Constraint(type_var=unpacked_type, op=self.direction, target=mapped) ] else: assert ( isinstance(unpacked_type, Instance) and unpacked_type.type.fullname == "builtins.tuple" ) - res = infer_constraints(unpacked_type, actual, self.direction) - assert isinstance(actual, Instance) # ensured by is_varlength_tuple == True + res = infer_constraints(unpacked_type, mapped, self.direction) for i, ti in enumerate(template.items): if i == unpack_index: # This one we just handled above. continue # For Tuple[T, *Ts, S] <: tuple[X, ...] infer also T <: X and S <: X. - res.extend(infer_constraints(ti, actual.args[0], self.direction)) + res.extend(infer_constraints(ti, mapped.args[0], self.direction)) return res else: assert isinstance(actual, TupleType) @@ -1377,8 +1423,18 @@ def visit_overloaded(self, template: Overloaded) -> list[Constraint]: def visit_type_type(self, template: TypeType) -> list[Constraint]: if isinstance(self.actual, CallableType): + if self.actual.is_type_obj(): + instance_type = self.actual.get_instance_type() + if self.erase_types: + instance_type = erase_typevars(instance_type) + return infer_constraints(template.item, instance_type, self.direction) return infer_constraints(template.item, self.actual.ret_type, self.direction) elif isinstance(self.actual, Overloaded): + if self.actual.is_type_obj(): + instance_type = self.actual.items[0].get_instance_type() + if self.erase_types: + instance_type = erase_typevars(instance_type) + return infer_constraints(template.item, instance_type, self.direction) return infer_constraints(template.item, self.actual.items[0].ret_type, self.direction) elif isinstance(self.actual, TypeType): return infer_constraints(template.item, self.actual.item, self.direction) diff --git a/mypy/copytype.py b/mypy/copytype.py index 9a390a01bdbab..12dda9d263975 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -107,7 +107,10 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: def visit_typeddict_type(self, t: TypedDictType) -> ProperType: return self.copy_common( - t, TypedDictType(t.items, t.required_keys, t.readonly_keys, t.fallback) + t, + TypedDictType( + t.items, t.required_keys, t.readonly_keys, t.fallback, is_closed=t.is_closed + ), ) def visit_literal_type(self, t: LiteralType) -> ProperType: diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 614b757c774d2..066fbf9bed2d6 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -192,13 +192,17 @@ def __init__(self, options: Options, status_file: str, timeout: int | None = Non options.show_traceback = True if options.use_fine_grained_cache: # Using fine_grained_cache implies generating and caring - # about the fine grained cache + # about the fine-grained cache. options.cache_fine_grained = True else: options.cache_dir = os.devnull # Fine-grained incremental doesn't support general partial types # (details in https://github.com/python/mypy/issues/4492) options.local_partial_types = True + # We don't support parallel checking in fine-grained mode yet. + # Ignore it for now, so that same config file can be used with + # mypy and with the daemon. + options.num_workers = 0 self.status_file = status_file # Since the object is created in the parent process we can check diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 5c28e8332a76c..06e716372f248 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -287,6 +287,13 @@ def __hash__(self) -> int: # This is a catch-all for remaining uncategorized errors. MISC: Final = ErrorCode("misc", "Miscellaneous other checks", "General") +CALL_ARG_MISC: Final = ErrorCode( + "call-arg", "Check number, names and kinds of arguments in calls", "General", sub_code_of=MISC +) +# CALL_ARG_MISC reuses the "call-arg" code string, so keep CALL_ARG as the canonical +# code that "call-arg" resolves to in the registry. +error_codes[CALL_ARG.code] = CALL_ARG + OVERLOAD_CANNOT_MATCH: Final = ErrorCode( "overload-cannot-match", "Warn if an @overload signature can never be matched", diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 5790b717172ac..fd507216a6be9 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -129,6 +129,9 @@ def freshen_function_type_vars(callee: F) -> F: tv = v.new_unification_variable(v) tvs.append(tv) tvmap[v.id] = tv + if tv.has_default(): + # Point to fresh ids in case defaults depend on previous variables. + tv.default = expand_type(tv.default, tvmap) fresh = expand_type(callee, tvmap).copy_modified(variables=tvs) return cast(F, fresh) else: @@ -182,7 +185,6 @@ class ExpandTypeVisitor(TrivialSyntheticTypeTranslator): def __init__(self, variables: Mapping[TypeVarId, Type]) -> None: super().__init__() self.variables = variables - self.recursive_tvar_guard: dict[TypeVarId, Type | None] | None = None def visit_unbound_type(self, t: UnboundType) -> Type: return t @@ -226,11 +228,11 @@ def visit_instance(self, t: Instance) -> Type: if t.type.fullname == "builtins.tuple": # Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...] arg = args[0] - if isinstance(arg, UnpackType): + if isinstance(arg, UnpackType) and not ( + isinstance(arg.type, TypeAliasType) and arg.type.is_recursive + ): unpacked = get_proper_type(arg.type) if isinstance(unpacked, Instance): - # TODO: this and similar asserts below may be unsafe because get_proper_type() - # may be called during semantic analysis before all invalid types are removed. assert unpacked.type.fullname == "builtins.tuple" args = list(unpacked.args) return t.copy_modified(args=args) @@ -245,16 +247,6 @@ def visit_type_var(self, t: TypeVarType) -> Type: # TODO: do we really need to do this? # If I try to remove this special-casing ~40 tests fail on reveal_type(). return repl.copy_modified(last_known_value=None) - if isinstance(repl, TypeVarType) and repl.has_default(): - if self.recursive_tvar_guard is None: - self.recursive_tvar_guard = {} - if (tvar_id := repl.id) in self.recursive_tvar_guard: - return self.recursive_tvar_guard[tvar_id] or repl - self.recursive_tvar_guard[tvar_id] = None - repl.default = repl.default.accept(self) - expanded = repl.accept(self) # Note: `expanded is repl` may be true. - repl = repl if isinstance(expanded, TypeVarType) else expanded - self.recursive_tvar_guard[tvar_id] = repl return repl def visit_param_spec(self, t: ParamSpecType) -> Type: @@ -344,7 +336,7 @@ def _possible_callable_kwargs(cls, repl: Parameters, dict_type: Instance) -> Pro return dict_type kwargs = {} required_names = set() - extra_items: Type = UninhabitedType() + extra_items: Type | None = None for kind, name, type in zip(repl.arg_kinds, repl.arg_names, repl.arg_types): if kind == ArgKind.ARG_NAMED and name is not None: kwargs[name] = type @@ -354,10 +346,11 @@ def _possible_callable_kwargs(cls, repl: Parameters, dict_type: Instance) -> Pro extra_items = type elif not kind.is_star() and name is not None: kwargs[name] = type - if not kwargs: + if not kwargs and extra_items is not None: return Instance(dict_type.type, [dict_type.args[0], extra_items]) - # TODO: when PEP 728 is implemented, pass extra_items below. - return TypedDictType(kwargs, required_names, set(), fallback=dict_type) + # TODO: when PEP 728 `extra_items` is implemented, pass extra_items below. + is_closed = extra_items is None + return TypedDictType(kwargs, required_names, set(), dict_type, is_closed=is_closed) def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: # Sometimes solver may need to expand a type variable with (a copy of) itself @@ -493,11 +486,16 @@ def visit_callable_type(self, t: CallableType) -> CallableType: arg_types = self.interpolate_args_for_unpack(t, var_arg.typ) else: arg_types = self.expand_types(t.arg_types) + instance_type = None + if t.instance_type is not None: + instance_type = t.instance_type.accept(self) + assert isinstance(instance_type, ProperType) expanded = t.copy_modified( arg_types=arg_types, ret_type=t.ret_type.accept(self), - type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None), - type_is=(t.type_is.accept(self) if t.type_is is not None else None), + type_guard=t.type_guard.accept(self) if t.type_guard is not None else None, + type_is=t.type_is.accept(self) if t.type_is is not None else None, + instance_type=instance_type, ) if needs_normalization: return expanded.with_normalized_var_args() @@ -538,7 +536,9 @@ def visit_tuple_type(self, t: TupleType) -> Type: if len(items) == 1: # Normalize Tuple[*Tuple[X, ...]] -> Tuple[X, ...] item = items[0] - if isinstance(item, UnpackType): + if isinstance(item, UnpackType) and not ( + isinstance(item.type, TypeAliasType) and item.type.is_recursive + ): unpacked = get_proper_type(item.type) if isinstance(unpacked, Instance): # expand_type() may be called during semantic analysis, before invalid unpacks are fixed. diff --git a/mypy/exportjson.py b/mypy/exportjson.py index c08f0f9f29118..7bd341401ce9d 100644 --- a/mypy/exportjson.py +++ b/mypy/exportjson.py @@ -128,8 +128,12 @@ def convert_symbol_table_node(self: SymbolTableNode, cfg: Config) -> Json: data["plugin_generated"] = True if self.cross_ref: data["cross_ref"] = self.cross_ref - elif self.node is not None: - data["node"] = convert_symbol_node(self.node, cfg) + else: + # Read the raw node without cross-reference fixup, since exportjson reads + # cache files in isolation and no node fixer is available. + node = self.read_node_no_fixup() + if node is not None: + data["node"] = convert_symbol_node(node, cfg) return data diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index 5a22b9c3c759a..1c9323be056dd 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -122,7 +122,7 @@ def expr_to_unanalyzed_type( else: base_fullname = expr.base.fullname - if base_fullname is not None and base_fullname in ANNOTATED_TYPE_NAMES: + if base_fullname is not None and base_fullname in ANNOTATED_TYPE_NAMES and args: # TODO: this is not the optimal solution as we are basically getting rid # of the Annotation definition and only returning the type information, # losing all the annotations. diff --git a/mypy/fixup.py b/mypy/fixup.py index c0782610e8f40..48ed7c26d57ba 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -279,6 +279,8 @@ def visit_callable_type(self, ct: CallableType) -> None: ct.type_guard.accept(self) if ct.type_is is not None: ct.type_is.accept(self) + if ct.instance_type is not None: + ct.instance_type.accept(self) def visit_overloaded(self, t: Overloaded) -> None: for ct in t.items: diff --git a/mypy/indirection.py b/mypy/indirection.py index c5f3fa89b8c4a..6bbda859de8f9 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -134,6 +134,12 @@ def visit_instance(self, t: types.Instance) -> None: def visit_callable_type(self, t: types.CallableType) -> None: self._visit_type_list(t.arg_types) self._visit(t.ret_type) + if t.type_guard is not None: + self._visit(t.type_guard) + if t.type_is is not None: + self._visit(t.type_is) + if t.instance_type is not None: + self._visit(t.instance_type) self._visit_type_tuple(t.variables) def visit_overloaded(self, t: types.Overloaded) -> None: diff --git a/mypy/infer.py b/mypy/infer.py index 56f4af753db82..2c155ee2456b3 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -70,8 +70,11 @@ def infer_type_arguments( actual: Type, is_supertype: bool = False, skip_unsatisfied: bool = False, + erase_types: bool = True, ) -> list[Type | None]: # Like infer_function_type_arguments, but only match a single type # against a generic type. - constraints = infer_constraints(template, actual, SUPERTYPE_OF if is_supertype else SUBTYPE_OF) + constraints = infer_constraints( + template, actual, SUPERTYPE_OF if is_supertype else SUBTYPE_OF, erase_types=erase_types + ) return solve_constraints(type_vars, constraints, skip_unsatisfied=skip_unsatisfied)[0] diff --git a/mypy/inspections.py b/mypy/inspections.py index 1a869438101fe..6e2018262944c 100644 --- a/mypy/inspections.py +++ b/mypy/inspections.py @@ -297,7 +297,7 @@ def cmp_types(x: TypeInfo, y: TypeInfo) -> int: result = {} for base in sorted_bases: if not combined_attrs[base]: - # Skip bases where everytihng was filtered out. + # Skip bases where everything was filtered out. continue result[base] = combined_attrs[base] return result diff --git a/mypy/join.py b/mypy/join.py index a8c9910e60bb7..c609b303bb087 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -35,6 +35,7 @@ TupleType, Type, TypeAliasType, + TypedDictItem, TypedDictType, TypeOfAny, TypeType, @@ -610,24 +611,61 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: else: return join_types(self.s, mypy.typeops.tuple_fallback(t)) + def resolve_typeddict_item( + self, item_name: str, s: TypedDictItem, t: TypedDictItem + ) -> tuple[Type | None, bool, bool]: + """Return the type, requiredness, and readonlyness of a join item. + + If the type is None, the item should be omitted from the join keys. + """ + is_required = s.required and t.required + + if s.typ is None or t.typ is None: + # Key is implicitly NotRequired[ReadOnly[object]] in one type and + # (object join T) == object. Omitting it in the join leaves it implicitly + # of object type. + join_type = None + is_readonly = True + else: + join_type = join_types(s.typ, t.typ) + + if s.required != t.required: + # As one of the input types marks the key as not required, it must + # be not required in the join supertype. However, as the other input + # type does not have a delitem overload for the key, the delitem + # overload must be omitted in the join supertype too. This can only + # be done by marking the key as readonly. + is_readonly = True + elif s.readonly or t.readonly: + # If either type has no setitem overload for this key, + # then the join supertype must also omit it + is_readonly = True + else: + is_readonly = not is_equivalent(s.typ, t.typ) + + return join_type, is_required, is_readonly + def visit_typeddict_type(self, t: TypedDictType) -> ProperType: if isinstance(self.s, TypedDictType): - items = { - item_name: s_item_type - for (item_name, s_item_type, t_item_type) in self.s.zip(t) - if ( - is_equivalent(s_item_type, t_item_type) - and (item_name in t.required_keys) == (item_name in self.s.required_keys) + items = {} + required_keys = set() + readonly_keys = set() + for item_name, s_item, t_item in self.s.zipall(t): + item_type, is_required, is_readonly = self.resolve_typeddict_item( + item_name, s_item, t_item ) - } + if item_type is not None: + items[item_name] = item_type + if is_required: + required_keys.add(item_name) + if is_readonly: + readonly_keys.add(item_name) + fallback = self.s.create_anonymous_fallback() - all_keys = set(items.keys()) - # We need to filter by items.keys() since some required keys present in both t and - # self.s might be missing from the join if the types are incompatible. - required_keys = all_keys & t.required_keys & self.s.required_keys - # If one type has a key as readonly, we mark it as readonly for both: - readonly_keys = (t.readonly_keys | t.readonly_keys) & all_keys - return TypedDictType(items, required_keys, readonly_keys, fallback) + is_closed = self.s.is_closed and t.is_closed + return TypedDictType( + items, required_keys, readonly_keys, fallback, is_closed=is_closed + ) elif isinstance(self.s, Instance): return join_types(self.s, t.fallback) else: @@ -773,10 +811,14 @@ def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: fallback = t.fallback else: fallback = s.fallback + instance_type = None + if t.instance_type is not None and s.instance_type is not None: + instance_type = join_types(t.instance_type, s.instance_type) return t.copy_modified( arg_types=arg_types, arg_names=combine_arg_names(t, s), ret_type=join_types(t.ret_type, s.ret_type), + instance_type=instance_type, fallback=fallback, name=None, ) @@ -827,10 +869,14 @@ def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: fallback = t.fallback else: fallback = s.fallback + instance_type = None + if t.instance_type is not None and s.instance_type is not None: + instance_type = join_types(t.instance_type, s.instance_type) return t.copy_modified( arg_types=arg_types, arg_names=combine_arg_names(t, s), ret_type=join_types(t.ret_type, s.ret_type), + instance_type=instance_type, fallback=fallback, name=None, ) diff --git a/mypy/main.py b/mypy/main.py index 9227e24680ce6..0cf624a3a5d7b 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -1677,13 +1677,15 @@ def process_cache_map( parser.error(f"Duplicate --cache-map source {source})") if not source.endswith(".py") and not source.endswith(".pyi"): parser.error(f"Invalid --cache-map source {source} (triple[0] must be *.py[i])") - if not meta_file.endswith(".meta.json"): + if not meta_file.endswith((".meta.json", ".meta.ff")): parser.error( - "Invalid --cache-map meta_file %s (triple[1] must be *.meta.json)" % meta_file + "Invalid --cache-map meta_file %s (triple[1] must be *.meta.json or *.meta.ff)" + % meta_file ) - if not data_file.endswith(".data.json"): + if not data_file.endswith((".data.json", ".data.ff")): parser.error( - "Invalid --cache-map data_file %s (triple[2] must be *.data.json)" % data_file + "Invalid --cache-map data_file %s (triple[2] must be *.data.json or *.data.ff)" + % data_file ) options.cache_map[source] = (meta_file, data_file) diff --git a/mypy/maptype.py b/mypy/maptype.py index 59ecb2bc99930..d9bb6da8080a0 100644 --- a/mypy/maptype.py +++ b/mypy/maptype.py @@ -16,25 +16,6 @@ def map_instance_to_supertype(instance: Instance, superclass: TypeInfo) -> Insta # Fast path: `instance` already belongs to `superclass`. return instance - if superclass.fullname == "builtins.tuple" and instance.type.tuple_type: - if has_type_vars(instance.type.tuple_type): - # We special case mapping generic tuple types to tuple base, because for - # such tuples fallback can't be calculated before applying type arguments. - alias = instance.type.special_alias - assert alias is not None - if not alias._is_recursive: - # Unfortunately we can't support this for generic recursive tuples. - # If we skip this special casing we will fall back to tuple[Any, ...]. - tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) - if isinstance(tuple_type, TupleType): - # Make the import here to avoid cyclic imports. - import mypy.typeops - - return mypy.typeops.tuple_fallback(tuple_type) - elif isinstance(tuple_type, Instance): - # This can happen after normalizing variadic tuples. - return tuple_type - if not superclass.type_vars: # Fast path: `superclass` has no type variables to map to. return Instance(superclass, []) @@ -93,6 +74,28 @@ def map_instance_to_direct_supertypes(instance: Instance, supertype: TypeInfo) - for b in typ.bases: if b.type == supertype: + + if supertype.fullname == "builtins.tuple" and instance.type.tuple_type: + if has_type_vars(instance.type.tuple_type): + # We special case mapping generic tuple types to tuple base, because for + # such tuples fallback can't be calculated before applying type arguments. + alias = instance.type.special_alias + assert alias is not None + if not alias._is_recursive: + # Unfortunately we can't support this for generic recursive tuples. + # If we skip this special casing we will fall back to tuple[Any, ...]. + tuple_type = expand_type_by_instance(instance.type.tuple_type, instance) + if isinstance(tuple_type, TupleType): + # Make the import here to avoid cyclic imports. + import mypy.typeops + + result.append(mypy.typeops.tuple_fallback(tuple_type)) + continue + elif isinstance(tuple_type, Instance): + # This can happen after normalizing variadic tuples. + result.append(tuple_type) + continue + t = expand_type_by_instance(b, instance) assert isinstance(t, Instance) result.append(t) diff --git a/mypy/meet.py b/mypy/meet.py index cb8ad75f6013d..bfc6d88a1e209 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -35,6 +35,7 @@ TupleType, Type, TypeAliasType, + TypedDictItem, TypedDictType, TypeGuardedType, TypeOfAny, @@ -973,7 +974,7 @@ def visit_callable_type(self, t: CallableType) -> ProperType: return result elif isinstance(self.s, TypeType) and t.is_type_obj() and not t.is_generic(): # In this case we are able to potentially produce a better meet. - res = meet_types(self.s.item, t.ret_type) + res = meet_types(self.s.item, t.get_instance_type()) if not isinstance(res, (NoneType, UninhabitedType)): return TypeType.make_normalized(res) return self.default(self.s) @@ -1090,7 +1091,28 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: elif isinstance(self.s, Instance): # meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>]. if self.s.type.fullname in TUPLE_LIKE_INSTANCE_NAMES and self.s.args: - return t.copy_modified(items=[meet_types(it, self.s.args[0]) for it in t.items]) + arg = self.s.args[0] + new_items: list[Type] = [] + for it in t.items: + # Unpack items need to be handled by the caller. + if isinstance(it, UnpackType): + unpacked = get_proper_type(it.type) + if isinstance(unpacked, TypeVarTupleType): + # We can't infer anything in this case. + new_arg = UninhabitedType() + instance = unpacked.tuple_fallback + else: + assert ( + isinstance(unpacked, Instance) + and unpacked.type.fullname == "builtins.tuple" + ) + new_arg = meet_types(unpacked.args[0], arg) + instance = unpacked + new_items.append(UnpackType(instance.copy_modified(args=[new_arg]))) + else: + # All other items can be processed in a regular way. + new_items.append(meet_types(it, arg)) + return t.copy_modified(items=new_items) elif is_proper_subtype(t, self.s): # A named tuple that inherits from a normal class return t @@ -1100,26 +1122,73 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: return t return self.default(self.s) + def resolve_typeddict_item_type( + self, name: str, s: TypedDictItem, t: TypedDictItem + ) -> tuple[Type | None, bool]: + """Return the type and readonlyness of a meet item. + + If the parent constraints are mutually incompatible, the + returned type will be None; the overall meet type should + be UninhabitedType. + """ + is_readonly = s.readonly and t.readonly + + if t.typ is None: + assert s.typ is not None + meet_type = s.typ + elif s.typ is None: + meet_type = t.typ + else: + meet_type = meet_types(s.typ, t.typ) + + if ( + s.typ is not None + and s.mutable + and (not is_equivalent(meet_type, s.typ) or (t.required and not s.required)) + ): + meet_type = None + elif ( + t.typ is not None + and t.mutable + and (not is_equivalent(meet_type, t.typ) or (s.required and not t.required)) + ): + meet_type = None + elif isinstance(get_proper_type(meet_type), UninhabitedType) and ( + s.required or t.required + ): + meet_type = None + + return (meet_type, is_readonly) + def visit_typeddict_type(self, t: TypedDictType) -> ProperType: if isinstance(self.s, TypedDictType): - for name, l, r in self.s.zip(t): - if not is_equivalent(l, r) or (name in t.required_keys) != ( - name in self.s.required_keys - ): + is_closed = self.s.is_closed or t.is_closed + items: dict[str, Type] = {} + readonly_keys: set[str] = set() + for name, s_item, t_item in self.s.zipall(t): + meet_type, is_readonly = self.resolve_typeddict_item_type(name, s_item, t_item) + + if meet_type is None: return self.default(self.s) - item_list: list[tuple[str, Type]] = [] - for item_name, s_item_type, t_item_type in self.s.zipall(t): - if s_item_type is not None: - item_list.append((item_name, s_item_type)) - else: - # at least one of s_item_type and t_item_type is not None - assert t_item_type is not None - item_list.append((item_name, t_item_type)) - items = dict(item_list) + + if ( + is_closed + and not is_readonly + and isinstance(get_proper_type(meet_type), UninhabitedType) + ): + # Simplify emitted type by omitting redundant Never keys from closed + # TypedDicts + continue + + items[name] = meet_type + if is_readonly: + readonly_keys.add(name) + fallback = self.s.create_anonymous_fallback() - required_keys = t.required_keys | self.s.required_keys - readonly_keys = t.readonly_keys | self.s.readonly_keys - return TypedDictType(items, required_keys, readonly_keys, fallback) + required_keys = self.s.required_keys | t.required_keys + return TypedDictType( + items, required_keys, readonly_keys, fallback, is_closed=is_closed + ) elif isinstance(self.s, Instance) and is_subtype(t, self.s): return t else: @@ -1182,9 +1251,16 @@ def meet_similar_callables(t: CallableType, s: CallableType) -> CallableType: fallback = t.fallback else: fallback = s.fallback + if t.instance_type is None: + instance_type = s.instance_type + elif s.instance_type is None: + instance_type = t.instance_type + else: + instance_type = meet_types(t.instance_type, s.instance_type) return t.copy_modified( arg_types=arg_types, ret_type=meet_types(t.ret_type, s.ret_type), + instance_type=instance_type, fallback=fallback, name=None, ) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 30ced27aef22f..2e40048cbb58b 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -180,6 +180,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage: IMPLICIT_GENERIC_ANY_BUILTIN: Final = ( 'Implicit generic "Any". Use "{}" and specify generic parameters' ) +NO_CYCLIC_DEFAULT: Final = "Cyclic type variable defaults are not supported" +NO_DEFAULT_AFTER_TYPEVAR_TUPLE: Final = "A type variable with default cannot follow TypeVarTuple" INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)" INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position" INVALID_PARAM_SPEC_LOCATION: Final = "Invalid location for ParamSpec {}" diff --git a/mypy/messages.py b/mypy/messages.py index 3de66c7c6082c..ffac78201a6cd 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -970,8 +970,9 @@ def too_many_positional_arguments(self, callee: CallableType, context: Context) msg = "Too many positional arguments" else: msg = "Too many positional arguments" + for_function(callee) - self.fail(msg, context) + self.fail(msg, context, code=codes.CALL_ARG_MISC) self.maybe_note_about_special_args(callee, context) + self.note_defined_here(callee, context, code=codes.CALL_ARG_MISC) def maybe_note_about_special_args(self, callee: CallableType, context: Context) -> None: if self.prefer_simple_messages(): @@ -1018,7 +1019,9 @@ def unexpected_keyword_argument( ) self.note_defined_here(callee, context) - def note_defined_here(self, callee: CallableType, context: Context) -> None: + def note_defined_here( + self, callee: CallableType, context: Context, code: ErrorCode = codes.CALL_ARG + ) -> None: module = find_defining_module(self.modules, callee) if ( module @@ -1031,7 +1034,7 @@ def note_defined_here(self, callee: CallableType, context: Context) -> None: fname = "Called function" else: fname = fname.split(" of ")[0] # use short method names in the note - self.note(f'{fname} defined in "{module.fullname}"', context, code=codes.CALL_ARG) + self.note(f'{fname} defined in "{module.fullname}"', context, code=code) def duplicate_argument_value(self, callee: CallableType, index: int, context: Context) -> None: self.fail( @@ -2220,8 +2223,9 @@ def report_protocol_problems( class_obj = False is_module = False skip = [] + original_subtype = subtype if isinstance(subtype, TupleType): - subtype = subtype.partial_fallback + subtype = mypy.typeops.tuple_fallback(subtype) elif isinstance(subtype, TypedDictType): subtype = subtype.fallback elif isinstance(subtype, TypeType): @@ -2231,18 +2235,20 @@ def report_protocol_problems( subtype = subtype.item elif isinstance(subtype, CallableType): if subtype.is_type_obj(): - ret_type = get_proper_type(subtype.ret_type) - if isinstance(ret_type, TupleType): - ret_type = ret_type.partial_fallback - if not isinstance(ret_type, Instance): + instance_type = subtype.get_instance_type(force_fallback=True) + if not isinstance(instance_type, Instance): return class_obj = True - subtype = ret_type + subtype = instance_type else: subtype = subtype.fallback skip = ["__call__"] if subtype.extra_attrs and subtype.extra_attrs.mod_name: is_module = True + if not isinstance(original_subtype, TupleType): + # Apart from instances, only tuples are supported by + # is_protocol_implementation() for now. + original_subtype = subtype # Report missing members missing = get_missing_protocol_members(subtype, supertype, skip=skip) @@ -2274,7 +2280,7 @@ def report_protocol_problems( # Report member type conflicts conflict_types = get_conflict_protocol_types( - subtype, supertype, class_obj=class_obj, options=self.options + subtype, original_subtype, supertype, class_obj=class_obj, options=self.options ) if conflict_types and ( not is_subtype(subtype, erase_type(supertype), options=self.options) @@ -2827,9 +2833,7 @@ def format_literal_value(typ: LiteralType) -> str: elif isinstance(typ, FunctionLike): func = typ if func.is_type_obj(): - # The type of a type object type can be derived from the - # return type (this always works). - return format(TypeType.make_normalized(func.items[0].ret_type)) + return format(TypeType.make_normalized(func.items[0].get_instance_type())) elif isinstance(func, CallableType): if func.type_guard is not None: return_type = f"TypeGuard[{format(func.type_guard)}]" @@ -3191,7 +3195,11 @@ def get_missing_protocol_members(left: Instance, right: Instance, skip: list[str def get_conflict_protocol_types( - left: Instance, right: Instance, class_obj: bool = False, options: Options | None = None + left: Instance, + original_left: Type, + right: Instance, + class_obj: bool = False, + options: Options | None = None, ) -> list[tuple[str, Type, Type, bool]]: """Find members that are defined in 'left' but have incompatible types. Return them as a list of ('member', 'got', 'expected', 'is_lvalue'). @@ -3203,7 +3211,7 @@ def get_conflict_protocol_types( continue supertype = find_member(member, right, left) assert supertype is not None - subtype = get_protocol_member(left, member, class_obj) + subtype = get_protocol_member(left, original_left, member, class_obj) if not subtype: continue is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True, options=options) @@ -3219,7 +3227,9 @@ def get_conflict_protocol_types( different_setter = True supertype = set_supertype if IS_EXPLICIT_SETTER in get_member_flags(member, left): - set_subtype = get_protocol_member(left, member, class_obj, is_lvalue=True) + set_subtype = get_protocol_member( + left, original_left, member, class_obj, is_lvalue=True + ) if set_subtype and not is_same_type(set_subtype, subtype): different_setter = True subtype = set_subtype diff --git a/mypy/mixedtraverser.py b/mypy/mixedtraverser.py index 535391886b020..40f3e640ef032 100644 --- a/mypy/mixedtraverser.py +++ b/mypy/mixedtraverser.py @@ -16,6 +16,7 @@ TypeApplication, TypedDictExpr, TypeFormExpr, + TypeInfo, TypeVarExpr, Var, WithStmt, @@ -41,15 +42,29 @@ def visit_func(self, o: FuncItem, /) -> None: self.visit_optional_type(o.type) def visit_class_def(self, o: ClassDef, /) -> None: - # TODO: Should we visit generated methods/variables as well, either here or in - # TraverserVisitor? super().visit_class_def(o) - info = o.info - if info: - for base in info.bases: - base.accept(self) - if info.special_alias: - info.special_alias.accept(self) + if o.info: + self.process_type_info(o.info) + + def process_type_info(self, info: TypeInfo) -> None: + # TODO: Should we visit generated methods/variables as well? + # We should for methods generated by us (see below). But it is less clear for + # 3rd party plugin generated methods (since we don't want to emit errors there). + for base in info.bases: + base.accept(self) + if info.special_alias: + # We need to accept all types that are conceptually identical like special + # alias target and corresponding tuple_type or typeddict_type, since those + # may be copies, and not the same object. + info.special_alias.accept(self) + if info.tuple_type: + info.tuple_type.accept(self) + if info.typeddict_type: + info.typeddict_type.accept(self) + if info.is_named_tuple or info.is_newtype: + for sym in info.names.values(): + if sym.plugin_generated and sym.node: + sym.node.accept(self) def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None: super().visit_type_alias_expr(o) @@ -64,12 +79,11 @@ def visit_type_var_expr(self, o: TypeVarExpr, /) -> None: def visit_typeddict_expr(self, o: TypedDictExpr, /) -> None: super().visit_typeddict_expr(o) - self.visit_optional_type(o.info.typeddict_type) + self.process_type_info(o.info) def visit_namedtuple_expr(self, o: NamedTupleExpr, /) -> None: super().visit_namedtuple_expr(o) - assert o.info.tuple_type - o.info.tuple_type.accept(self) + self.process_type_info(o.info) def visit__promote_expr(self, o: PromoteExpr, /) -> None: super().visit__promote_expr(o) @@ -77,6 +91,8 @@ def visit__promote_expr(self, o: PromoteExpr, /) -> None: def visit_newtype_expr(self, o: NewTypeExpr, /) -> None: super().visit_newtype_expr(o) + if o.info: + self.process_type_info(o.info) self.visit_optional_type(o.old_type) # Statements diff --git a/mypy/nativeparse.py b/mypy/nativeparse.py index d048e9bce65e2..d5af1c8c6ec8d 100644 --- a/mypy/nativeparse.py +++ b/mypy/nativeparse.py @@ -167,8 +167,9 @@ class State: - def __init__(self, options: Options) -> None: + def __init__(self, options: Options, is_stub: bool = False) -> None: self.options = options + self.is_stub = is_stub self.errors: list[ParseError] = [] self.num_funcs = 0 @@ -180,9 +181,35 @@ def add_error( {"line": line, "column": column, "message": message, "blocker": blocker, "code": code} ) + def check_min_version( + self, + feature: str, + min_version: tuple[int, int], + line: int, + column: int, + *, + enforce_in_stubs: bool = False, + ) -> None: + """Report a non blocker syntax error if the target Python feature is older than min_version.""" + if self.is_stub and not enforce_in_stubs: + return + if self.options.python_version < min_version: + curr = self.options.python_version + self.add_error( + f"{feature}: requires Python {min_version[0]}.{min_version[1]} or newer " + f"(current target: Python {curr[0]}.{curr[1]})", + line, + column, + blocker=False, + code="syntax", + ) + def native_parse( - filename: str, options: Options, skip_function_bodies: bool = False + filename: str, + options: Options, + source: str | bytes | None = None, + skip_function_bodies: bool = False, ) -> tuple[MypyFile, list[ParseError], TypeIgnores]: """Parse a Python file using the native Rust-based parser. @@ -211,7 +238,7 @@ def native_parse( uses_template_strings, source_hash, mypy_comments, - ) = parse_to_binary_ast(filename, options, skip_function_bodies) + ) = parse_to_binary_ast(filename, options, source, skip_function_bodies) node = MypyFile([], []) node.path = filename node.raw_data = FileRawData( @@ -248,7 +275,10 @@ def read_statements(state: State, data: ReadBuffer, n: int) -> list[Statement]: def parse_to_binary_ast( - filename: str, options: Options, skip_function_bodies: bool = False + filename: str, + options: Options, + source: str | bytes | None = None, + skip_function_bodies: bool = False, ) -> tuple[bytes, list[ParseError], TypeIgnores, bytes, bool, bool, str, list[tuple[int, str]]]: # This is a horrible hack to work around a mypyc bug where imported # module may be not ready in a thread sometimes. @@ -259,6 +289,7 @@ def parse_to_binary_ast( raise ImportError("Cannot import ast_serialize") ast_bytes, errors, ignores, import_bytes, ast_data = ast_serialize.parse( filename, + source, skip_function_bodies=skip_function_bodies, python_version=options.python_version, platform=options.platform, @@ -600,6 +631,13 @@ def read_parameters(state: State, data: ReadBuffer) -> tuple[list[Argument], boo return arguments, has_ann +def check_type_param_defaults( + state: State, type_params: list[TypeParam], line: int, column: int +) -> None: + if any(p.default is not None for p in type_params): + state.check_min_version("Type parameter defaults", (3, 13), line, column) + + def read_type_params(state: State, data: ReadBuffer) -> list[TypeParam]: """Read type parameters (PEP 695 generics).""" type_params: list[TypeParam] = [] @@ -673,6 +711,11 @@ def read_func_def(state: State, data: ReadBuffer) -> FuncDef: if is_async: func_def.is_coroutine = True read_loc(data, func_def) + if type_params: + state.check_min_version( + "Improved type parameter syntax", (3, 12), func_def.line, func_def.column + ) + check_type_param_defaults(state, type_params, func_def.line, func_def.column) if typ: typ.line = func_def.line typ.column = func_def.column @@ -720,6 +763,11 @@ def read_class_def(state: State, data: ReadBuffer) -> ClassDef: ) class_def.decorators = decorators read_loc(data, class_def) + if type_params: + state.check_min_version( + "Improved type parameter syntax", (3, 12), class_def.line, class_def.column + ) + check_type_param_defaults(state, type_params, class_def.line, class_def.column) expect_end_tag(data) return class_def @@ -774,6 +822,8 @@ def read_type_alias_stmt(state: State, data: ReadBuffer) -> TypeAliasStmt: stmt = TypeAliasStmt(name, type_params, lambda_expr) read_loc(data, stmt) + state.check_min_version('"type" statements', (3, 12), stmt.line, stmt.column) + check_type_param_defaults(state, type_params, stmt.line, stmt.column) expect_end_tag(data) return stmt @@ -825,6 +875,10 @@ def read_try_stmt(state: State, data: ReadBuffer) -> TryStmt: stmt = TryStmt(body, vars_list, types_list, handlers, else_body, finally_body) stmt.is_star = is_star read_loc(data, stmt) + if is_star: + state.check_min_version("Exception groups", (3, 11), stmt.line, stmt.column) + if state.options.python_version < (3, 11): + stmt.is_star = False expect_end_tag(data) return stmt @@ -960,6 +1014,8 @@ def read_type(state: State, data: ReadBuffer) -> Type: from_star_syntax = read_bool(data) unpack = UnpackType(inner_type, from_star_syntax=from_star_syntax) read_loc(data, unpack) + if from_star_syntax: + state.check_min_version("Star unpack syntax", (3, 11), unpack.line, unpack.column) expect_end_tag(data) return unpack elif tag == types.CALL_TYPE: @@ -1467,6 +1523,9 @@ def read_expression(state: State, data: ReadBuffer) -> Expression: titems.append(s) expr = TemplateStrExpr(titems) read_loc(data, expr) + state.check_min_version( + "t-strings", (3, 14), expr.line, expr.column, enforce_in_stubs=True + ) expect_end_tag(data) return expr elif tag == nodes.LAMBDA_EXPR: diff --git a/mypy/nodes.py b/mypy/nodes.py index 32a694560b24b..e2ea348d2df11 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -49,6 +49,7 @@ WriteBuffer, read_bool, read_bytes, + read_flags, read_int, read_int_list, read_int_opt, @@ -61,6 +62,7 @@ read_tag, write_bool, write_bytes, + write_flags, write_int, write_int_list, write_int_opt, @@ -3145,7 +3147,15 @@ class TypeVarLikeExpr(SymbolNode, Expression): Note that they are constructed by the semantic analyzer. """ - __slots__ = ("_name", "_fullname", "upper_bound", "default", "variance", "is_new_style") + __slots__ = ( + "_name", + "_fullname", + "upper_bound", + "default", + "variance", + "is_new_style", + "default_depends", + ) _name: str _fullname: str @@ -3160,6 +3170,9 @@ class TypeVarLikeExpr(SymbolNode, Expression): # TypeVar(..., contravariant=True) defines a contravariant type # variable. variance: int + # Record instances and type aliases that appear bare/implicit in the default value + # of this type variable. This is needed to detect recursive type variable defaults. + default_depends: set[TypeInfo | TypeAlias] | None def __init__( self, @@ -3178,6 +3191,7 @@ def __init__( self.default = default self.variance = variance self.is_new_style = is_new_style + self.default_depends = None @property def name(self) -> str: @@ -3655,6 +3669,8 @@ class is generic then it will be a type constructor of higher kind. "is_type_check_only", "deprecated", "type_object_type", + "default_depends", + "typeddict_data", ) _fullname: str # Fully qualified name @@ -3816,6 +3832,19 @@ class is generic then it will be a type constructor of higher kind. # appears in runtime context. type_object_type: mypy.types.FunctionLike | None + # Type variables whose defaults depend on defaults of type variables in other classes + # and type aliases. We keep track of this to safely handle situations like this one: + # class C[T = D]: ... + # class D[S = C]: ... + # x: C + # Since we apply fix_instance() eagerly, inferring a precise type is quite tricky. + # Therefore, we infer the type of `x` as `C[D[Any]]` to avoid infinite recursion. + # Keys are type variable full names. + default_depends: dict[str, set[TypeAlias | TypeInfo]] + + # If defn is TypedDictType, stores information needed for delayed validation of inheritance. + typeddict_data: TypedDictData | None + FLAGS: Final = [ "is_abstract", "is_enum", @@ -3877,6 +3906,8 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None self.is_type_check_only = False self.deprecated = None self.type_object_type = None + self.default_depends = {} + self.typeddict_data = None def add_type_vars(self) -> None: self.has_type_var_tuple_type = False @@ -4103,6 +4134,12 @@ def has_base(self, fullname: str) -> bool: return True return False + def get_base(self, fullname: str) -> TypeInfo: + for cls in self.mro: + if cls.fullname == fullname: + return cls + assert False, f"Missing base {fullname} for {self.fullname}" + def direct_base_classes(self) -> list[TypeInfo]: """Return a direct base classes. @@ -4542,6 +4579,7 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here "eager", "tvar_tuple_index", "python_3_12_type_alias", + "default_depends", ) __match_args__ = ("name", "target", "alias_tvars", "no_args") @@ -4574,6 +4612,8 @@ def __init__( self.eager = eager self.python_3_12_type_alias = python_3_12_type_alias self.tvar_tuple_index = None + # This plays the same role as TypeInfo.default_depends attribute. + self.default_depends: dict[str, set[TypeAlias | TypeInfo]] = {} for i, t in enumerate(alias_tvars): if isinstance(t, mypy.types.TypeVarTupleType): self.tvar_tuple_index = i @@ -4912,6 +4952,17 @@ def node(self) -> SymbolNode | None: self.unfixed = False return self._node + def read_node_no_fixup(self) -> SymbolNode | None: + """Return the deserialized node without performing cross-reference fixup. + + This is intended for introspection tools (such as mypy.exportjson) that read + cache files in isolation, where no node fixer is available. + """ + if self._node is None and self._node_bytes: + self._node = read_symbol(ReadBuffer(self._node_bytes), self._node_tag) + self._node_bytes = b"" + return self._node + def copy(self) -> SymbolTableNode: new = SymbolTableNode( self.kind, self._node, self.module_public, self.implicit, self.module_hidden @@ -5197,6 +5248,44 @@ def read(cls, data: ReadBuffer) -> DataclassTransformSpec: return ret +class TypedDictFieldSource: + """Source of a TypedDict field definition, used for forming error messages. + + May be defined directly on the type, or on a base class. + """ + + __slots__ = ("base", "ctx") + + base: TypeInfo | None + ctx: Context + + def __init__(self, base: TypeInfo | None, ctx: Context) -> None: + self.base = base + self.ctx = ctx + + +class TypedDictData: + """Stores information needed for delayed validation of TypedDict inheritance.""" + + __slots__ = ("ready", "bases", "field_sources") + + # If False, the type definition referenced a placeholder + ready: bool + + bases: list[tuple[TypeInfo, dict[str, mypy.types.Type]]] + field_sources: dict[str, TypedDictFieldSource] + + def __init__( + self, + ready: bool, + bases: list[tuple[TypeInfo, dict[str, mypy.types.Type]]], + field_sources: dict[str, TypedDictFieldSource], + ) -> None: + self.ready = ready + self.bases = bases + self.field_sources = field_sources + + @trait class SplittingVisitor: # If True, process function definitions. If False, don't. This is used @@ -5226,20 +5315,6 @@ def set_flags(node: Node, flags: list[str]) -> None: setattr(node, name, True) -def write_flags(data: WriteBuffer, flags: list[bool]) -> None: - assert len(flags) <= 26, "This many flags not supported yet" - packed = 0 - for i, flag in enumerate(flags): - if flag: - packed |= 1 << i - write_int(data, packed) - - -def read_flags(data: ReadBuffer, num_flags: int) -> list[bool]: - packed = read_int(data) - return [(packed & (1 << i)) != 0 for i in range(num_flags)] - - def get_member_expr_fullname(expr: MemberExpr) -> str | None: """Return the qualified name representation of a member expression. @@ -5390,6 +5465,16 @@ def set_info(node: SymbolNode, info: TypeInfo) -> None: set_info(node.impl, info) +def func_scoped_name(name: str, line: int) -> str: + """Mangled name to use when storing function-scoped symbols in global symbol tables.""" + return f"{name}@{line}" + + +def inline_base(name: str, index: int) -> str: + """Synthetic name to use when storing inlined base classes in symbol tables.""" + return f"{name}@base{index + 1}" + + # See docstring for mypy/cache.py for reserved tag ranges. MYPY_FILE: Final[Tag] = 50 OVERLOADED_FUNC_DEF: Final[Tag] = 51 diff --git a/mypy/options.py b/mypy/options.py index 2f03cd3eab5b7..92c9ea3b1701d 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -1,9 +1,9 @@ from __future__ import annotations +import os import pprint import re import sys -import sysconfig from collections.abc import Callable from re import Pattern from typing import Any, Final @@ -112,11 +112,11 @@ def __init__(self) -> None: # then mypy does not search for PEP 561 packages. self.python_executable: str | None = sys.executable - # When cross compiling to emscripten, we need to rely on MACHDEP because - # sys.platform is the host build platform, not emscripten. - MACHDEP = sysconfig.get_config_var("MACHDEP") - if MACHDEP == "emscripten": - self.platform = MACHDEP + # When cross compiling to emscripten, sys.platform is the host build + # platform, not emscripten. Pyodide sets the PYODIDE environment variable + # at build time, so we use it to detect the emscripten target. + if "PYODIDE" in os.environ: + self.platform = "emscripten" else: self.platform = sys.platform diff --git a/mypy/parse.py b/mypy/parse.py index b0901a3a24552..47e2f95f0f307 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -12,12 +12,11 @@ def parse( - source: str | bytes, + source: str | bytes | None, fnam: str, module: str | None, errors: Errors, options: Options, - file_exists: bool, eager: bool = False, ) -> MypyFile: """Parse a source file, without doing any semantic analysis. @@ -27,28 +26,29 @@ def parse( New parser returns empty tree with serialized data. To get the full tree and the parse errors, use eager=True. + + `source` must not be `None` if the old parser is used. The new parser will read and + parse contents from path `fnam` if `source` is `None`. """ if options.native_parser: - # Native parser only works with actual files on disk - # Fall back to fastparse for in-memory source or non-existent files - if file_exists: - import mypy.nativeparse - - ignore_errors = options.ignore_errors or fnam in errors.ignored_files - # If errors are ignored, we can drop many function bodies to speed up type checking. - strip_function_bodies = ignore_errors and not options.preserve_asts - tree, _, _ = mypy.nativeparse.native_parse( - fnam, options, skip_function_bodies=strip_function_bodies - ) - # Set is_stub based on file extension - tree.is_stub = fnam.endswith(".pyi") - # Note: tree.imports is populated directly by load_from_raw() with deserialized - # import metadata, so we don't need to collect imports via AST traversal - if eager and tree.raw_data is not None: - tree = load_from_raw(fnam, module, tree.raw_data, errors, options) - return tree - # Fall through to fastparse for non-existent files - + import mypy.nativeparse + + ignore_errors = options.ignore_errors or fnam in errors.ignored_files + # If errors are ignored, we can drop many function bodies to speed up type checking. + strip_function_bodies = ignore_errors and not options.preserve_asts + tree, _, _ = mypy.nativeparse.native_parse( + fnam, options, source, skip_function_bodies=strip_function_bodies + ) + # Set is_stub based on file extension + tree.is_stub = fnam.endswith(".pyi") + # Note: tree.imports is populated directly by load_from_raw() with deserialized + # import metadata, so we don't need to collect imports via AST traversal + if eager and tree.raw_data is not None: + tree = load_from_raw(fnam, module, tree.raw_data, errors, options) + return tree + + if source is None: + raise ValueError("Source cannot be `None` when using the old parser") if options.transform_source is not None: source = options.transform_source(source) import mypy.fastparse @@ -71,7 +71,7 @@ def load_from_raw( """ from mypy.nativeparse import State, deserialize_imports, read_statements - state = State(options) + state = State(options, is_stub=fnam.endswith(".pyi")) if imports_only: defs = [] else: diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5b39b11a623cc..790fe618c95c7 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -914,7 +914,7 @@ def _infer_dataclass_attr_init_type( # Perform a simple-minded inference from the signature of __set__, if present. # We can't use mypy.checkmember here, since this plugin runs before type checking. - # We only support some basic scanerios here, which is hopefully sufficient for + # We only support some basic scenarios here, which is hopefully sufficient for # the vast majority of use cases. if not isinstance(t, Instance): return default diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 8023397836124..a7d1c9e4426ab 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -293,9 +293,10 @@ def typed_dict_get_callback(ctx: MethodContext) -> Type: for key in keys: value_type: Type | None = ctx.type.items.get(key) if value_type is None: - return ctx.default_return_type - - if key in ctx.type.required_keys: + if not ctx.type.is_closed: + return ctx.default_return_type + output_types.append(default_type) + elif key in ctx.type.required_keys: output_types.append(value_type) else: # HACK to deal with get(key, {}) diff --git a/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index a513b91ff309b..9a5576c17e82c 100644 --- a/mypy/plugins/singledispatch.py +++ b/mypy/plugins/singledispatch.py @@ -126,7 +126,7 @@ def singledispatch_register_callback(ctx: MethodContext) -> Type: # is_subtype doesn't work when the right type is Overloaded, so we need the # actual type - register_type = first_arg_type.items[0].ret_type + register_type = first_arg_type.items[0].get_instance_type() type_args = RegisterCallableInfo(register_type, ctx.type) register_callable = make_fake_register_class_instance(ctx.api, type_args) return register_callable diff --git a/mypy/semanal.py b/mypy/semanal.py index a958043fa35c2..e010273b0781f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -188,8 +188,10 @@ WithStmt, YieldExpr, YieldFromExpr, + func_scoped_name, get_member_expr_fullname, implicit_module_attrs, + inline_base, is_final_node, type_aliases, type_aliases_source_versions, @@ -364,13 +366,6 @@ # string literal as a type expression. _MULTIPLE_WORDS_NONTYPE_RE = re.compile(r'\s*[^\s.\'"|\[]+\s+[^\s.\'"|\[]') -# Matches any valid Python identifier, including identifiers with Unicode characters. -# -# [^\d\W] = word character that is not a digit -# \w = word character -# \Z = match end of string; does not allow a trailing \n, unlike $ -_IDENTIFIER_RE = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE) - class SemanticAnalyzer( NodeVisitor[None], SemanticAnalyzerInterface, SemanticAnalyzerPluginInterface, SplittingVisitor @@ -546,8 +541,18 @@ def __init__( # import foo.bar self.transitive_submodule_imports: dict[str, set[str]] = {} + # Instances and type aliases that were fixed using default values of type + # variables. This can be used on-demand by type analyzer. Use record_fixed_type() + # to create the set lazily. + self.types_fixed: set[TypeInfo | TypeAlias] | None = None + + # Stack of type variables that have been removed from current class because they + # cannot be bound unambiguously. This can happen if a (regular) type variable + # with a default follows a type variable tuple. + self.removed_type_vars: list[list[TypeVarType]] = [[]] + # mypyc doesn't properly handle implementing an abstractproperty - # with a regular attribute so we make them properties + # with a regular attribute, so we make them properties @property def type(self) -> TypeInfo | None: return self._type @@ -1151,7 +1156,8 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: leading_type = fill_typevars(info) if func.is_class or func.name == "__new__": leading_type = self.class_type(leading_type) - func.type = replace_implicit_first_type(functype, leading_type) + if not has_placeholder(leading_type): + func.type = replace_implicit_first_type(functype, leading_type) elif has_self_type and isinstance(func.unanalyzed_type, CallableType): if not isinstance(get_proper_type(func.unanalyzed_type.arg_types[0]), AnyType): if self.is_expected_self_type( @@ -1836,8 +1842,9 @@ def visit_class_def(self, defn: ClassDef) -> None: if self.push_type_args(defn.type_args, defn) is None: self.mark_incomplete(defn.name, defn) return - + self.removed_type_vars.append([]) self.analyze_class(defn) + self.removed_type_vars.pop() self.pop_type_args(defn.type_args) self.incomplete_type_stack.pop() @@ -1890,6 +1897,9 @@ def analyze_type_param( upper_bound = self.named_type("builtins.tuple", [self.object_type()]) else: upper_bound = self.object_type() + # Reset fixed types both before and after each collection just in case. + if self.types_fixed is not None: + self.types_fixed.clear() if type_param.default: default = self.anal_type( type_param.default, @@ -1899,6 +1909,7 @@ def analyze_type_param( allow_param_spec_literals=type_param.kind == PARAM_SPEC_KIND, allow_tuple_literal=type_param.kind == PARAM_SPEC_KIND, allow_unpack=type_param.kind == TYPE_VAR_TUPLE_KIND, + analyzing_tvar_def=True, ) if default is None: default = PlaceholderType(None, [], context.line) @@ -1910,6 +1921,8 @@ def analyze_type_param( default = self.check_typevartuple_default(default, type_param.default) else: default = AnyType(TypeOfAny.from_omitted_generics) + default_depends = self.types_fixed + self.types_fixed = None if type_param.kind == TYPE_VAR_KIND: values: list[Type] = [] if type_param.values: @@ -1922,7 +1935,7 @@ def analyze_type_param( values.append(AnyType(TypeOfAny.from_error)) else: values.append(analyzed) - return TypeVarExpr( + tv = TypeVarExpr( name=type_param.name, fullname=fullname, values=values, @@ -1933,7 +1946,7 @@ def analyze_type_param( line=context.line, ) elif type_param.kind == PARAM_SPEC_KIND: - return ParamSpecExpr( + tv = ParamSpecExpr( name=type_param.name, fullname=fullname, upper_bound=upper_bound, @@ -1944,7 +1957,7 @@ def analyze_type_param( else: assert type_param.kind == TYPE_VAR_TUPLE_KIND tuple_fallback = self.named_type("builtins.tuple", [self.object_type()]) - return TypeVarTupleExpr( + tv = TypeVarTupleExpr( name=type_param.name, fullname=fullname, upper_bound=upper_bound, @@ -1953,6 +1966,8 @@ def analyze_type_param( is_new_style=True, line=context.line, ) + tv.default_depends = default_depends + return tv def pop_type_args(self, type_args: list[TypeParam] | None) -> None: if not type_args: @@ -1979,27 +1994,25 @@ def analyze_class(self, defn: ClassDef) -> None: self.infer_metaclass_and_bases_from_compat_helpers(defn) bases = defn.base_type_exprs - bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables( - defn, bases, context=defn + bases, tvar_defs, is_protocol, declared_tvars = ( + self.clean_up_bases_and_infer_type_variables(defn, bases, context=defn) ) self.check_type_alias_bases(bases) - - for tvd in tvar_defs: - if isinstance(tvd, TypeVarType) and any( - has_placeholder(t) for t in [tvd.upper_bound] + tvd.values - ): - # Some type variable bounds or values are not ready, we need - # to re-analyze this class. - self.defer() - if has_placeholder(tvd.default): - # Placeholder values in TypeVarLikeTypes may get substituted in. - # Defer current target until they are ready. - self.mark_incomplete(defn.name, defn) - return + default_depends: dict[str, set[TypeAlias | TypeInfo]] = {} + for _, tv in declared_tvars: + if tv.default_depends is not None: + default_depends[tv.fullname] = tv.default_depends + + if any(has_placeholder(tvd) for tvd in tvar_defs): + # Some type variable bounds or values are not ready, we need to + # re-analyze this class. Note we force progress to handle cases like + # class C[T = C], this matches logic in process_typevar_parameters() + # for "old style" type variables. + self.defer(force_progress=tvar_defs != defn.type_vars) self.analyze_class_keywords(defn) - bases_result = self.analyze_base_classes(bases) + bases_result = self.analyze_base_classes(defn.name, bases) if bases_result is None or self.found_incomplete_ref(tag): # Something was incomplete. Defer current target. self.mark_incomplete(defn.name, defn) @@ -2011,6 +2024,10 @@ def analyze_class(self, defn: ClassDef) -> None: # are okay in nested positions, since they can't affect the MRO. self.mark_incomplete(defn.name, defn) return + if any(has_placeholder(base) for base, _ in base_types): + # We need to manually call defer() in case a placeholder was brought by a + # type variable default, so that type analyzer didn't call it. + self.defer() declared_metaclass, should_defer, any_meta = self.get_declared_metaclass( defn.name, defn.metaclass @@ -2024,14 +2041,18 @@ def analyze_class(self, defn: ClassDef) -> None: if defn.info: self.setup_type_vars(defn, tvar_defs) self.setup_alias_type_vars(defn) + defn.info.default_depends = default_depends return if self.analyze_namedtuple_classdef(defn, tvar_defs): + if defn.info: + defn.info.default_depends = default_depends return # Create TypeInfo for class now that base classes and the MRO can be calculated. self.prepare_class_def(defn) self.setup_type_vars(defn, tvar_defs) + defn.info.default_depends = default_depends if base_error: defn.info.fallback_to_any = True if any_meta: @@ -2070,7 +2091,21 @@ def check_type_alias_bases(self, bases: list[Expression]) -> None: ) def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None: - defn.type_vars = tvar_defs + seen_tvt = False + valid_tvar_defs = [] + for tv in tvar_defs: + if seen_tvt and isinstance(tv, TypeVarType) and tv.has_default(): + self.fail( + message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE, defn, code=codes.TYPE_VAR + ) + # Remove the ambiguous type variable, and record it, so that we can replace + # all its uses with Any. + self.removed_type_vars[-1].append(tv) + continue + if isinstance(tv, TypeVarTupleType): + seen_tvt = True + valid_tvar_defs.append(tv) + defn.type_vars = valid_tvar_defs defn.info.type_vars = [] # we want to make sure any additional logic in add_type_vars gets run defn.info.add_type_vars() @@ -2108,20 +2143,22 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: if ( defn.info and defn.info.typeddict_type - and not has_placeholder(defn.info.typeddict_type) + and (defn.info.typeddict_data is None or defn.info.typeddict_data.ready) ): - # This is a valid TypedDict, and it is fully analyzed. - return True - is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn) + # Don't reprocess everything + is_typeddict = True + info = defn.info + else: + is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn) if is_typeddict: - for decorator in defn.decorators: - decorator.accept(self) - if info is not None: - self.analyze_class_decorator_common(defn, info, decorator) if info is None: self.mark_incomplete(defn.name, defn) else: - self.prepare_class_def(defn, info, custom_names=True) + self.prepare_class_def(defn, info) + for decorator in defn.decorators: + decorator.accept(self) + if defn.info: + self.analyze_class_decorator_common(defn, decorator) return True return False @@ -2141,19 +2178,19 @@ def analyze_namedtuple_classdef( info: TypeInfo | None = defn.info else: is_named_tuple, info = self.named_tuple_analyzer.analyze_namedtuple_classdef( - defn, self.is_stub_file, self.is_func_scope() + defn, self.is_stub_file ) if is_named_tuple: if info is None: self.mark_incomplete(defn.name, defn) else: - self.prepare_class_def(defn, info, custom_names=True) + self.prepare_class_def(defn, info) self.setup_type_vars(defn, tvar_defs) self.setup_alias_type_vars(defn) with self.scope.class_scope(defn.info): for deco in defn.decorators: deco.accept(self) - self.analyze_class_decorator_common(defn, defn.info, deco) + self.analyze_class_decorator_common(defn, deco) with self.named_tuple_analyzer.save_namedtuple_body(info): self.analyze_class_body_common(defn) return True @@ -2235,7 +2272,7 @@ def leave_class(self) -> None: def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None: decorator.accept(self) - self.analyze_class_decorator_common(defn, defn.info, decorator) + self.analyze_class_decorator_common(defn, decorator) if isinstance(decorator, RefExpr): if decorator.fullname in RUNTIME_PROTOCOL_DECOS: if defn.info.is_protocol: @@ -2247,13 +2284,12 @@ def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None ): defn.info.dataclass_transform_spec = self.parse_dataclass_transform_spec(decorator) - def analyze_class_decorator_common( - self, defn: ClassDef, info: TypeInfo, decorator: Expression - ) -> None: + def analyze_class_decorator_common(self, defn: ClassDef, decorator: Expression) -> None: """Common method for applying class decorators. Called on regular classes, typeddicts, and namedtuples. """ + info = defn.info if refers_to_fullname(decorator, FINAL_DECORATOR_NAMES): info.is_final = True elif refers_to_fullname(decorator, DISJOINT_BASE_DECORATOR_NAMES): @@ -2270,7 +2306,7 @@ def analyze_class_decorator_common( def clean_up_bases_and_infer_type_variables( self, defn: ClassDef, base_type_exprs: list[Expression], context: Context - ) -> tuple[list[Expression], list[TypeVarLikeType], bool]: + ) -> tuple[list[Expression], list[TypeVarLikeType], bool, list[tuple[str, TypeVarLikeExpr]]]: """Remove extra base classes such as Generic and infer type vars. For example, consider this class: @@ -2282,7 +2318,8 @@ class Foo(Bar, Generic[T]): ... Note that this is performed *before* semantic analysis. - Returns (remaining base expressions, inferred type variables, is protocol). + Returns a tuple: + (remaining base expressions, type variables, is protocol, type variable expressions). """ removed: list[int] = [] declared_tvars: TypeVarLikeList = [] @@ -2362,7 +2399,7 @@ class Foo(Bar, Generic[T]): ... defn.removed_base_type_exprs.append(defn.base_type_exprs[i]) del base_type_exprs[i] tvar_defs = self.tvar_defs_from_tvars(declared_tvars, context) - return base_type_exprs, tvar_defs, is_protocol + return base_type_exprs, tvar_defs, is_protocol, declared_tvars def analyze_class_typevar_declaration( self, base: Type, has_type_var_tuple: bool @@ -2409,6 +2446,9 @@ def analyze_unbound_tvar(self, t: Type) -> tuple[str, TypeVarLikeExpr] | None: if isinstance(t, UnboundType): sym = self.lookup_qualified(t.name, t) if sym and sym.fullname in UNPACK_TYPE_NAMES: + if not t.args: + # Unpack used without arguments, e.g. `Protocol[Unpack]` + return None inner_t = t.args[0] if isinstance(inner_t, UnboundType): return self.analyze_unbound_tvar_impl(inner_t, is_unpacked=True) @@ -2469,9 +2509,6 @@ def tvar_defs_from_tvars( tvar_defs: list[TypeVarLikeType] = [] last_tvar_name_with_default: str | None = None for name, tvar_expr in tvars: - tvar_expr.default = tvar_expr.default.accept( - TypeVarDefaultTranslator(self, tvar_expr.name, context) - ) # PEP-695 type variables that are redeclared in an inner scope are warned # about elsewhere. if not tvar_expr.is_new_style and not self.tvar_scope.allow_binding( @@ -2481,6 +2518,11 @@ def tvar_defs_from_tvars( message_registry.TYPE_VAR_REDECLARED_IN_NESTED_CLASS.format(name), context ) tvar_def = self.tvar_scope.bind_new(name, tvar_expr, self.fail, context) + # Fix any residual UnboundTypes in the generated TypeVarLike, keep + # TypeVarLikeExpr untouched as it may be shared by multiple classes. + tvar_def.default = tvar_def.default.accept( + TypeVarDefaultTranslator(self, tvar_expr.name, context) + ) if last_tvar_name_with_default is not None and not tvar_def.has_default(): self.msg.tvar_without_default_type( tvar_def.name, last_tvar_name_with_default, context @@ -2513,51 +2555,26 @@ def get_and_bind_all_tvars(self, type_exprs: list[Expression]) -> list[TypeVarLi tvar_defs.append(tvar_def) return tvar_defs - def prepare_class_def( - self, defn: ClassDef, info: TypeInfo | None = None, custom_names: bool = False - ) -> None: + def class_fullname(self, name: str, line: int) -> str: + if not self.is_nested_within_func_scope(): + return self.qualified_name(name) + name = func_scoped_name(name, line) + return f"{self.cur_mod_id}.{name}" + + def prepare_class_def(self, defn: ClassDef, info: TypeInfo | None = None) -> None: """Prepare for the analysis of a class definition. Create an empty TypeInfo and store it in a symbol table, or if the 'info' argument is provided, store it instead (used for magic type definitions). """ if not defn.info: - defn.fullname = self.qualified_name(defn.name) - # TODO: Nested classes + defn.fullname = self.class_fullname(defn.name, defn.line) info = info or self.make_empty_type_info(defn) defn.info = info info.defn = defn - if not custom_names: - # Some special classes (in particular NamedTuples) use custom fullname logic. - # Don't override it here (also see comment below, this needs cleanup). - if not self.is_func_scope(): - info._fullname = self.qualified_name(defn.name) - else: - info._fullname = info.name - local_name = defn.name - if "@" in local_name: - local_name = local_name.split("@")[0] - self.add_symbol(local_name, defn.info, defn) + self.add_symbol(defn.name, defn.info, defn) if self.is_nested_within_func_scope(): - # We need to preserve local classes, let's store them - # in globals under mangled unique names - # - # TODO: Putting local classes into globals breaks assumptions in fine-grained - # incremental mode and we should avoid it. In general, this logic is too - # ad-hoc and needs to be removed/refactored. - if "@" not in defn.info._fullname: - global_name = defn.info.name + "@" + str(defn.line) - defn.info._fullname = self.cur_mod_id + "." + global_name - else: - # Preserve name from previous fine-grained incremental run. - global_name = defn.info.name - defn.fullname = defn.info._fullname - if defn.info.is_named_tuple or defn.info.typeddict_type: - # Named tuples and Typed dicts nested within a class are stored - # in the class symbol table. - self.add_symbol_skip_local(global_name, defn.info) - else: - self.globals[global_name] = SymbolTableNode(GDEF, defn.info) + self.add_global_symbol(defn.name, defn, defn.info) def make_empty_type_info(self, defn: ClassDef) -> TypeInfo: if ( @@ -2588,7 +2605,7 @@ def get_name_repr_of_expr(self, expr: Expression) -> str | None: return None def analyze_base_classes( - self, base_type_exprs: list[Expression] + self, cls_name: str, base_type_exprs: list[Expression] ) -> tuple[list[tuple[ProperType, Expression]], bool] | None: """Analyze base class types. @@ -2600,7 +2617,7 @@ def analyze_base_classes( """ is_error = False bases = [] - for base_expr in base_type_exprs: + for i, base_expr in enumerate(base_type_exprs): if ( isinstance(base_expr, RefExpr) and base_expr.fullname in TYPED_NAMEDTUPLE_NAMES + TPDICT_NAMES @@ -2618,7 +2635,10 @@ def analyze_base_classes( try: base = self.expr_to_analyzed_type( - base_expr, allow_placeholder=True, allow_type_any=True + base_expr, + allow_placeholder=True, + allow_type_any=True, + unique_name=inline_base(cls_name, i), ) except TypeTranslationError: name = self.get_name_repr_of_expr(base_expr) @@ -3595,7 +3615,7 @@ def analyze_enum_assign(self, s: AssignmentStmt) -> bool: # This is an analyzed enum definition. # It is valid iff it can be stored correctly, failures were already reported. return self._is_single_name_assignment(s) - return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope()) + return self.enum_call_analyzer.process_enum_call(s) def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: """Check if s defines a namedtuple.""" @@ -3619,7 +3639,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: namespace = self.qualified_name(name) with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple( - s.rvalue, name, self.is_func_scope() + s.rvalue, name ) if internal_name is None: return False @@ -3656,7 +3676,7 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: namespace = self.qualified_name(name) with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)): is_typed_dict, info, tvar_defs = self.typed_dict_analyzer.check_typeddict( - s.rvalue, name, self.is_func_scope() + s.rvalue, name ) if not is_typed_dict: return False @@ -3983,11 +4003,14 @@ def analyze_alias( declared_type_vars: TypeVarLikeList | None = None, all_declared_type_params_names: list[str] | None = None, python_3_12_type_alias: bool = False, - ) -> tuple[Type | None, list[TypeVarLikeType], set[str], bool]: + ) -> tuple[ + Type | None, list[TypeVarLikeType], set[str], bool, dict[str, set[TypeAlias | TypeInfo]] + ]: """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable). If yes, return the corresponding type, a list of type variables for generic aliases, - a set of names the alias depends on, and True if the original type has empty tuple index. + a set of names the alias depends on, whether the original type has empty tuple index, + and any type variables whose defaults depend on other classes or type aliases. An example for the dependencies: A = int B = str @@ -4003,7 +4026,7 @@ def analyze_alias( self.fail( "Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE ) - return None, [], set(), False + return None, [], set(), False, {} found_type_vars = self.find_type_var_likes(typ) namespace = self.qualified_name(name) @@ -4015,6 +4038,28 @@ def analyze_alias( with self.allow_unbound_tvars_set(): rvalue.accept(self) + new_tvar_defs = [] + erase_tvar_defs = [] + variadic = False + for td in tvar_defs: + if variadic and isinstance(td, TypeVarType) and td.has_default(): + self.fail( + message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE, + rvalue, + code=codes.TYPE_VAR, + ) + # Remove the ambiguous type variable, and record it, so that we can + # replace all its uses with Any. + erase_tvar_defs.append(td) + continue + if isinstance(td, TypeVarTupleType): + # There can be only one variadic variable at most, + # the error is reported elsewhere. + if variadic: + continue + variadic = True + new_tvar_defs.append(td) + analyzed, depends_on = analyze_type_alias( typ, self, @@ -4027,22 +4072,17 @@ def analyze_alias( in_dynamic_func=dynamic, global_scope=global_scope, allowed_alias_tvars=tvar_defs, + erase_tvar_defs=erase_tvar_defs, alias_type_params_names=all_declared_type_params_names, python_3_12_type_alias=python_3_12_type_alias, ) - # There can be only one variadic variable at most, the error is reported elsewhere. - new_tvar_defs = [] - variadic = False - for td in tvar_defs: - if isinstance(td, TypeVarTupleType): - if variadic: - continue - variadic = True - new_tvar_defs.append(td) - indexed = bool(isinstance(typ, UnboundType) and (typ.args or typ.empty_tuple_index)) - return analyzed, new_tvar_defs, depends_on, indexed + default_depends = {} + for _, tv in alias_type_vars: + if tv.default_depends is not None: + default_depends[tv.fullname] = tv.default_depends + return analyzed, new_tvar_defs, depends_on, indexed, default_depends def is_pep_613(self, s: AssignmentStmt) -> bool: if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType): @@ -4142,9 +4182,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: alias_tvars: list[TypeVarLikeType] = [] depends_on: set[str] = set() indexed = False + default_depends: dict[str, set[TypeAlias | TypeInfo]] = {} else: tag = self.track_incomplete_refs() - res, alias_tvars, depends_on, indexed = self.analyze_alias( + res, alias_tvars, depends_on, indexed, default_depends = self.analyze_alias( lvalue.name, rvalue, allow_placeholder=True, @@ -4167,6 +4208,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # may appear in nested positions), therefore use becomes_typeinfo=True. self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True) return True + self.add_type_alias_deps(depends_on) check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s) # When this type alias gets "inlined", the Any is not explicit anymore, @@ -4205,6 +4247,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: eager=eager, python_3_12_type_alias=pep_695, ) + alias_node.default_depends = default_depends if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)): # Note: CallExpr is for "void = type(None)" and OpExpr is for "X | Y" union syntax. if not isinstance(s.rvalue.analyzed, TypeAliasExpr): @@ -4220,19 +4263,22 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # An alias gets updated. updated = False if isinstance(existing.node, TypeAlias): + # Invalidate recursive status cache in case it was previously set. + existing.node._is_recursive = None if existing.node.target != res: # Copy expansion to the existing alias, this matches how we update base classes # for a TypeInfo _in place_ if there are nested placeholders. existing.node.target = res + existing.node.default_depends = default_depends existing.node.alias_tvars = alias_tvars existing.node.no_args = no_args updated = True - # Invalidate recursive status cache in case it was previously set. - existing.node._is_recursive = None else: # Otherwise just replace existing placeholder with type alias *in place*. existing._node = alias_node updated = True + # TODO: switch type aliases to if has_placeholder(): process_placeholder() pattern. + # Type aliases are last notable exception from this logic. if updated: if self.final_iteration: self.cannot_resolve_name(lvalue.name, "name", s) @@ -4756,6 +4802,8 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: n_values = call.arg_kinds[1:].count(ARG_POS) values = self.analyze_value_types(call.args[1 : 1 + n_values]) + if self.types_fixed is not None: + self.types_fixed.clear() res = self.process_typevar_parameters( call.args[1 + n_values :], call.arg_names[1 + n_values :], @@ -4763,6 +4811,8 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: n_values, s, ) + default_depends = self.types_fixed + self.types_fixed = None if res is None: return False variance, upper_bound, default = res @@ -4803,6 +4853,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: type_var = TypeVarExpr( name, self.qualified_name(name), values, upper_bound, default, variance ) + type_var.default_depends = default_depends type_var.line = call.line call.analyzed = type_var updated = True @@ -4816,6 +4867,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: call.analyzed.upper_bound = upper_bound call.analyzed.values = values call.analyzed.default = default + call.analyzed.default_depends = default_depends if any(has_placeholder(v) for v in values): self.process_placeholder(None, "TypeVar values", s, force_progress=updated) elif has_placeholder(upper_bound): @@ -4968,6 +5020,11 @@ def process_typevar_parameters( variance = INVARIANT return variance, upper_bound, default + def record_fixed_type(self, fixed: TypeInfo | TypeAlias) -> None: + if self.types_fixed is None: + self.types_fixed = set() + self.types_fixed.add(fixed) + def get_typevarlike_argument( self, typevarlike_name: str, @@ -4979,7 +5036,7 @@ def get_typevarlike_argument( allow_param_spec_literals: bool = False, allow_unpack: bool = False, report_invalid_typevar_arg: bool = True, - ) -> ProperType | None: + ) -> Type | None: try: # We want to use our custom error message below, so we suppress # the default error message for invalid types here. @@ -4990,6 +5047,7 @@ def get_typevarlike_argument( allow_unbound_tvars=allow_unbound_tvars, allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, + analyzing_tvar_def=param_name == "default", ) if analyzed is None: # Type variables are special: we need to place them in the symbol table @@ -4999,15 +5057,19 @@ def get_typevarlike_argument( # class Custom(Generic[T]): # ... analyzed = PlaceholderType(None, [], context.line) - typ = get_proper_type(analyzed) - if report_invalid_typevar_arg and isinstance(typ, AnyType) and typ.is_from_error: + if ( + report_invalid_typevar_arg + and isinstance(analyzed, ProperType) + and isinstance(analyzed, AnyType) + and analyzed.is_from_error + ): self.fail( message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name), param_value, ) # Note: we do not return 'None' here -- we want to continue # using the AnyType. - return typ + return analyzed except TypeTranslationError: if report_invalid_typevar_arg: self.fail( @@ -5052,6 +5114,8 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: if n_values != 0: self.fail('Too many positional arguments for "ParamSpec"', s) + if self.types_fixed is not None: + self.types_fixed.clear() default: Type = AnyType(TypeOfAny.from_omitted_generics) for param_value, param_name in zip( call.args[1 + n_values :], call.arg_names[1 + n_values :] @@ -5076,6 +5140,8 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: "The variance and bound arguments to ParamSpec do not have defined semantics yet", s, ) + default_depends = self.types_fixed + self.types_fixed = None # PEP 612 reserves the right to define bound, covariant and contravariant arguments to # ParamSpec in a later PEP. If and when that happens, we should do something @@ -5086,12 +5152,14 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: name, self.qualified_name(name), self.object_type(), default, INVARIANT ) paramspec_var.line = call.line + paramspec_var.default_depends = default_depends call.analyzed = paramspec_var updated = True else: assert isinstance(call.analyzed, ParamSpecExpr) updated = default != call.analyzed.default call.analyzed.default = default + call.analyzed.default_depends = default_depends if has_placeholder(default): self.process_placeholder(None, "ParamSpec default", s, force_progress=updated) @@ -5114,6 +5182,8 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: self.fail('Too many positional arguments for "TypeVarTuple"', s) default: Type = AnyType(TypeOfAny.from_omitted_generics) + if self.types_fixed is not None: + self.types_fixed.clear() for param_value, param_name in zip( call.args[1 + n_values :], call.arg_names[1 + n_values :] ): @@ -5132,6 +5202,9 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: else: self.fail(f'Unexpected keyword argument "{param_name}" for "TypeVarTuple"', s) + default_depends = self.types_fixed + self.types_fixed = None + name = self.extract_typevarlike_name(s, call) if name is None: return False @@ -5149,12 +5222,14 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: INVARIANT, ) typevartuple_var.line = call.line + typevartuple_var.default_depends = default_depends call.analyzed = typevartuple_var updated = True else: assert isinstance(call.analyzed, TypeVarTupleExpr) updated = default != call.analyzed.default call.analyzed.default = default + call.analyzed.default_depends = default_depends if has_placeholder(default): self.process_placeholder(None, "TypeVarTuple default", s, force_progress=updated) @@ -5162,17 +5237,18 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: return True def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo: - if self.is_func_scope() and not self.type and "@" not in name: - name += "@" + str(line) class_def = ClassDef(name, Block([])) - if self.is_func_scope() and not self.type: - # Full names of generated classes should always be prefixed with the module names - # even if they are nested in a function, since these classes will be (de-)serialized. - # (Note that the caller should append @line to the name to avoid collisions.) - # TODO: clean this up, see #6422. - class_def.fullname = self.cur_mod_id + "." + self.qualified_name(name) - else: - class_def.fullname = self.qualified_name(name) + # Ground rules for classes nested in functions: + # * Use is_nested_within_func_scope(), not is_func_scope(), to determine whether + # to use any special logic, because nothing inside top-level functions is serialized. + # * ClassDef.name is not mangled (i.e. @line suffix is not appended). + # * ClassDef.fullname, and thus TypeInfo.fullname are always pkg.mod.Name@line, any + # "intermediate" classes are not included in the fullname. + # * The caller is responsible for storing the generated TypeInfo twice: once as usual + # with add_symbol(), and once using add_global_symbol() using the mangled name. + # The second one is needed to properly serialize any classes nested in functions. + # TODO: make sure the daemon works well with these rules. + class_def.fullname = self.class_fullname(name, line) info = TypeInfo(SymbolTable(), class_def, self.cur_mod_id) class_def.info = info @@ -5685,7 +5761,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: return tag = self.track_incomplete_refs() - res, alias_tvars, depends_on, indexed = self.analyze_alias( + res, alias_tvars, depends_on, indexed, default_depends = self.analyze_alias( s.name.name, s.value.expr(), allow_placeholder=True, @@ -5712,13 +5788,10 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) return - # Now go through all new variables and temporary replace all tvars that still - # refer to some placeholders. We defer the whole alias and will revisit it again, - # as well as all its dependents. - for i, tv in enumerate(alias_tvars): - if has_placeholder(tv): - self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) - alias_tvars[i] = self._trivial_typevarlike_like(tv) + if any(has_placeholder(tv) for tv in alias_tvars): + # Defer the alias if some type variables are not ready, same as for classes. + # Note: progress is forced below (if needed). + self.defer() self.add_type_alias_deps(depends_on) check_for_explicit_any( @@ -5732,7 +5805,10 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: res = make_any_non_unimported(res) eager = self.is_func_scope() if isinstance(res, ProperType) and isinstance(res, Instance): - fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options) + if not validate_instance(res, self.fail, indexed): + fix_instance( + res, self.fail, self.note, disallow_any=False, options=self.options + ) alias_node = TypeAlias( res, self.qualified_name(s.name.name), @@ -5744,6 +5820,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: eager=eager, python_3_12_type_alias=True, ) + alias_node.default_depends = default_depends s.alias_node = alias_node if ( @@ -5753,6 +5830,8 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: ): updated = False if isinstance(existing.node, TypeAlias): + # Invalidate recursive status cache in case it was previously set. + existing.node._is_recursive = None if ( existing.node.target != res or existing.node.alias_tvars != alias_node.alias_tvars @@ -5760,10 +5839,9 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: # Copy expansion to the existing alias, this matches how we update base classes # for a TypeInfo _in place_ if there are nested placeholders. existing.node.target = res + existing.node.default_depends = default_depends existing.node.alias_tvars = alias_tvars updated = True - # Invalidate recursive status cache in case it was previously set. - existing.node._is_recursive = None else: # Otherwise just replace existing placeholder with type alias *in place*. existing._node = alias_node @@ -5786,46 +5864,6 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: finally: self.pop_type_args(s.type_args) - def _trivial_typevarlike_like(self, tv: TypeVarLikeType) -> TypeVarLikeType: - object_type = self.named_type("builtins.object") - if isinstance(tv, TypeVarType): - return TypeVarType( - tv.name, - tv.fullname, - tv.id, - values=[], - upper_bound=object_type, - default=AnyType(TypeOfAny.from_omitted_generics), - variance=tv.variance, - line=tv.line, - column=tv.column, - ) - elif isinstance(tv, TypeVarTupleType): - tuple_type = self.named_type("builtins.tuple", [object_type]) - return TypeVarTupleType( - tv.name, - tv.fullname, - tv.id, - upper_bound=tuple_type, - tuple_fallback=tuple_type, - default=AnyType(TypeOfAny.from_omitted_generics), - line=tv.line, - column=tv.column, - ) - elif isinstance(tv, ParamSpecType): - return ParamSpecType( - tv.name, - tv.fullname, - tv.id, - flavor=tv.flavor, - upper_bound=object_type, - default=AnyType(TypeOfAny.from_omitted_generics), - line=tv.line, - column=tv.column, - ) - else: - assert False, f"Unknown TypeVarLike: {tv!r}" - # # Expressions # @@ -6395,7 +6433,7 @@ def analyze_comp_for(self, expr: GeneratorExpr | DictionaryComprehension) -> Non if i > 0: sequence.accept(self) # Bind index variables. - self.analyze_lvalue(index) + self.analyze_lvalue(index, is_index_var=True) for cond in conditions: cond.accept(self) @@ -7031,27 +7069,18 @@ def add_symbol( name, symbol, context, can_defer, escape_comprehensions, no_progress, type_param ) - def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None: - """Same as above, but skipping the local namespace. + def add_global_symbol(self, name: str, ctx: Context, node: SymbolNode) -> None: + """Add symbol to a global namespace. This doesn't check for previous definition and is only used - for serialization of method-level classes. + for serialization of classes nested in functions/methods. Classes defined within methods can be exposed through an attribute type, but method-level symbol tables aren't serialized. This method can be used to add such classes to an enclosing, serialized symbol table. """ - # TODO: currently this is only used by named tuples and typed dicts. - # Use this method also by normal classes, see issue #6422. - if self.type is not None: - names = self.type.names - kind = MDEF - else: - names = self.globals - kind = GDEF - symbol = SymbolTableNode(kind, node) - names[name] = symbol + self.globals[func_scoped_name(name, ctx.line)] = SymbolTableNode(GDEF, node) def add_symbol_table_node( self, @@ -7112,8 +7141,10 @@ def add_symbol_table_node( if isinstance(old, Var) and is_init_only(old): if old.has_explicit_value: self.fail("InitVar with default value cannot be redefined", context) - elif not ( - isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new) + elif ( + not (isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new)) + # Avoid (additional) errors for internal symbols. + and "@" not in name ): self.name_already_defined(name, context, existing) elif type_param or ( @@ -7711,14 +7742,16 @@ def expr_to_analyzed_type( allow_unbound_tvars: bool = False, allow_param_spec_literals: bool = False, allow_unpack: bool = False, + unique_name: str | None = None, + analyzing_tvar_def: bool = False, ) -> Type | None: - if isinstance(expr, CallExpr): + if unique_name is not None and isinstance(expr, CallExpr): # This is a legacy syntax intended mostly for Python 2, we keep it for # backwards compatibility, but new features like generic named tuples # and recursive named tuples will be not supported. expr.accept(self) internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple( - expr, None, self.is_func_scope() + expr, unique_name ) if tvar_defs: self.fail("Generic named tuples are not supported for legacy class syntax", expr) @@ -7742,6 +7775,7 @@ def expr_to_analyzed_type( allow_unbound_tvars=allow_unbound_tvars, allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, + analyzing_tvar_def=analyzing_tvar_def, ) def analyze_type_expr(self, expr: Expression) -> None: @@ -7769,6 +7803,7 @@ def type_analyzer( prohibit_self_type: str | None = None, prohibit_special_class_field_types: str | None = None, allow_type_any: bool = False, + analyzing_tvar_def: bool = False, ) -> TypeAnalyser: if tvar_scope is None: tvar_scope = self.tvar_scope @@ -7790,6 +7825,8 @@ def type_analyzer( prohibit_self_type=prohibit_self_type, prohibit_special_class_field_types=prohibit_special_class_field_types, allow_type_any=allow_type_any, + analyzing_tvar_def=analyzing_tvar_def, + erase_tvar_defs=self.removed_type_vars[-1], ) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack @@ -7816,6 +7853,7 @@ def anal_type( prohibit_self_type: str | None = None, prohibit_special_class_field_types: str | None = None, allow_type_any: bool = False, + analyzing_tvar_def: bool = False, ) -> Type | None: """Semantically analyze a type. @@ -7853,6 +7891,7 @@ def anal_type( prohibit_self_type=prohibit_self_type, prohibit_special_class_field_types=prohibit_special_class_field_types, allow_type_any=allow_type_any, + analyzing_tvar_def=analyzing_tvar_def, ) tag = self.track_incomplete_refs() typ = typ.accept(a) @@ -8042,16 +8081,9 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None: return elif isinstance(maybe_type_expr, StrExpr): str_value = maybe_type_expr.value # cache - # Filter out string literals with common patterns that could not - # possibly be in a type expression - if _MULTIPLE_WORDS_NONTYPE_RE.match(str_value): - # A common pattern in string literals containing a sentence. - # But cannot be a type expression. - maybe_type_expr.as_type = None - return # Filter out string literals which look like an identifier but # cannot be a type expression, for a few common reasons - if _IDENTIFIER_RE.fullmatch(str_value): + if str_value.isidentifier(): sym = self.lookup(str_value, UnboundType(str_value), suppress_errors=True) if sym is None: # Does not refer to anything in the local symbol table @@ -8077,13 +8109,21 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None: return else: # does not look like an identifier if '"' in str_value or "'" in str_value: - # Only valid inside a Literal[...] type + # Only valid inside a Literal[...] or Annotated[..., ...] type if "[" not in str_value: - # Cannot be a Literal[...] type + # Cannot be a Literal[...] or Annotated[..., ...] type maybe_type_expr.as_type = None return - elif str_value == "": - # Empty string is not a valid type + elif len(str_value) < 2 or str_value.isspace(): + # Whitespace-only strings cannot be valid types. Very short strings can + # only be valid if they are identifiers, but we already checked for those. + maybe_type_expr.as_type = None + return + # Filter out string literals with common patterns that could not + # possibly be in a type expression + if _MULTIPLE_WORDS_NONTYPE_RE.match(str_value): + # A common pattern in string literals containing a sentence. + # But cannot be a type expression. maybe_type_expr.as_type = None return elif isinstance(maybe_type_expr, IndexExpr): diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index b1e267b4c781f..4074a586a29f7 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import Final, cast +from typing import Final from mypy.nodes import ( ARG_NAMED, @@ -60,7 +60,7 @@ def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: self.options = options self.api = api - def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: + def process_enum_call(self, s: AssignmentStmt) -> bool: """Check if s defines an Enum; if yes, store the definition in symbol table. Return True if this looks like an Enum definition (but maybe with errors), @@ -70,7 +70,7 @@ def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: return False lvalue = s.lvalues[0] name = lvalue.name - enum_call = self.check_enum_call(s.rvalue, name, is_func_scope) + enum_call = self.check_enum_call(s.rvalue, name) if enum_call is None: return False if isinstance(lvalue, MemberExpr): @@ -80,9 +80,7 @@ def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: self.api.add_symbol(name, enum_call, s) return True - def check_enum_call( - self, node: Expression, var_name: str, is_func_scope: bool - ) -> TypeInfo | None: + def check_enum_call(self, node: Expression, var_name: str) -> TypeInfo | None: """Check if a call defines an Enum. Example: @@ -110,23 +108,15 @@ class A(enum.Enum): ) if not ok: # Error. Construct dummy return value. - name = var_name - if is_func_scope: - name += "@" + str(call.line) - info = self.build_enum_call_typeinfo(name, [], fullname, node.line) + info = self.build_enum_call_typeinfo(var_name, [], fullname, node.line) else: if new_class_name != var_name: msg = f'String argument 1 "{new_class_name}" to {fullname}(...) does not match variable name "{var_name}"' self.fail(msg, call) - - name = cast(StrExpr, call.args[0]).value - if name != var_name or is_func_scope: - # Give it a unique name derived from the line number. - name += "@" + str(call.line) - info = self.build_enum_call_typeinfo(name, items, fullname, call.line) + info = self.build_enum_call_typeinfo(var_name, items, fullname, call.line) # Store generated TypeInfo under both names, see semanal_namedtuple for more details. - if name != var_name or is_func_scope: - self.api.add_symbol_skip_local(name, info) + if self.api.is_nested_within_func_scope(): + self.api.add_global_symbol(var_name, node, info) call.analyzed = EnumCallExpr(info, items, values) call.analyzed.set_line(call) info.line = node.line diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 08fc3e2bb7725..6244cb8150907 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -105,7 +105,7 @@ def __init__( self.msg = msg def analyze_namedtuple_classdef( - self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool + self, defn: ClassDef, is_stub_file: bool ) -> tuple[bool, TypeInfo | None]: """Analyze if given class definition can be a named tuple definition. @@ -122,8 +122,6 @@ def analyze_namedtuple_classdef( # This is a valid named tuple, but some types are incomplete. return True, None items, types, default_items, statements = result - if is_func_scope and "@" not in defn.name: - defn.name += "@" + str(defn.line) existing_info = None if isinstance(defn.analyzed, NamedTupleExpr): existing_info = defn.analyzed.info @@ -221,12 +219,13 @@ def check_namedtuple_classdef( return items, types, default_items, statements def check_namedtuple( - self, node: Expression, var_name: str | None, is_func_scope: bool + self, node: Expression, name: str ) -> tuple[str | None, TypeInfo | None, list[TypeVarLikeType]]: """Check if a call defines a namedtuple. - The optional var_name argument is the name of the variable to - which this is assigned, if any. + The name argument is the name of the variable to which this is assigned. + For an inlined base class this is a unique name generated from class name + base number. Return a tuple of two items: * Internal name of the named tuple (e.g. the name passed as an argument to namedtuple) @@ -254,42 +253,17 @@ def check_namedtuple( items, types, defaults, typename, tvar_defs, ok = result else: # Error. Construct dummy return value. - if var_name: - name = var_name - if is_func_scope: - name += "@" + str(call.line) - else: - name = var_name = "namedtuple@" + str(call.line) info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line, None) - self.store_namedtuple_info(info, var_name, call, is_typed) - if name != var_name or is_func_scope: - # NOTE: we skip local namespaces since they are not serialized. - self.api.add_symbol_skip_local(name, info) - return var_name, info, [] + self.store_namedtuple_info(info, name, call, is_typed) + if self.api.is_nested_within_func_scope(): + # NOTE: we always serialize in global namespace for convenience, + # because local namespaces are never serialized. + self.api.add_global_symbol(name, call, info) + return name, info, [] if not ok: # This is a valid named tuple but some types are not ready. - return typename, None, [] - - # We use the variable name as the class name if it exists. If - # it doesn't, we use the name passed as an argument. We prefer - # the variable name because it should be unique inside a - # module, and so we don't need to disambiguate it with a line - # number. - if var_name: - name = var_name - else: - name = typename - - if var_name is None or is_func_scope: - # There are two special cases where need to give it a unique name derived - # from the line number: - # * This is a base class expression, since it often matches the class name: - # class NT(NamedTuple('NT', [...])): - # ... - # * This is a local (function or method level) named tuple, since - # two methods of a class can define a named tuple with the same name, - # and they will be stored in the same namespace (see below). - name += "@" + str(call.line) + return name, None, [] + if defaults: default_items = { arg_name: default for arg_name, default in zip(items[-len(defaults) :], defaults) @@ -304,29 +278,9 @@ def check_namedtuple( name, items, types, default_items, node.line, existing_info ) - # If var_name is not None (i.e. this is not a base class expression), we always - # store the generated TypeInfo under var_name in the current scope, so that - # other definitions can use it. - if var_name: - self.store_namedtuple_info(info, var_name, call, is_typed) - else: - call.analyzed = NamedTupleExpr(info, is_typed=is_typed) - call.analyzed.set_line(call) - # There are three cases where we need to store the generated TypeInfo - # second time (for the purpose of serialization): - # * If there is a name mismatch like One = NamedTuple('Other', [...]) - # we also store the info under name 'Other@lineno', this is needed - # because classes are (de)serialized using their actual fullname, not - # the name of l.h.s. - # * If this is a method level named tuple. It can leak from the method - # via assignment to self attribute and therefore needs to be serialized - # (local namespaces are not serialized). - # * If it is a base class expression. It was not stored above, since - # there is no var_name (but it still needs to be serialized - # since it is in MRO of some class). - if name != var_name or is_func_scope: - # NOTE: we skip local namespaces since they are not serialized. - self.api.add_symbol_skip_local(name, info) + self.store_namedtuple_info(info, name, call, is_typed) + if self.api.is_nested_within_func_scope(): + self.api.add_global_symbol(name, call, info) return typename, info, tvar_defs def store_namedtuple_info( diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index e1d62c4410c9f..2735832cc0fe7 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -67,9 +67,6 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: name = var_name # OK, now we know this is a NewType. But the base type may be not ready yet, # add placeholder as we do for ClassDef. - - if self.api.is_func_scope(): - name += "@" + str(s.line) fullname = self.api.qualified_name(name) if not call.analyzed or isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info: @@ -134,8 +131,8 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: else: call.analyzed.info.bases = newtype_class_info.bases self.api.add_symbol(var_name, call.analyzed.info, s) - if self.api.is_func_scope(): - self.api.add_symbol_skip_local(name, call.analyzed.info) + if self.api.is_nested_within_func_scope(): + self.api.add_global_symbol(var_name, s, call.analyzed.info) newtype_class_info.line = s.line return True diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index a85d4ed00b5e6..f4ec7eb2c5d55 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -24,6 +24,7 @@ SymbolNode, SymbolTable, SymbolTableNode, + TypeAlias, TypeInfo, ) from mypy.plugin import SemanticAnalyzerPluginInterface @@ -84,6 +85,10 @@ def lookup_fully_qualified(self, fullname: str, /) -> SymbolTableNode: def lookup_fully_qualified_or_none(self, fullname: str, /) -> SymbolTableNode | None: raise NotImplementedError + @abstractmethod + def record_fixed_type(self, fixed: TypeInfo | TypeAlias) -> None: + raise NotImplementedError + @abstractmethod def fail( self, @@ -137,6 +142,10 @@ def is_stub_file(self) -> bool: def is_func_scope(self) -> bool: raise NotImplementedError + @abstractmethod + def is_nested_within_func_scope(self) -> bool: + raise NotImplementedError + @property @abstractmethod def type(self) -> TypeInfo | None: @@ -230,13 +239,8 @@ def add_symbol( raise NotImplementedError @abstractmethod - def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None: - """Add symbol to the current symbol table, skipping locals. - - This is used to store symbol nodes in a symbol table that - is going to be serialized (local namespaces are not serialized). - See implementation docstring for more details. - """ + def add_global_symbol(self, name: str, ctx: Context, node: SymbolNode) -> None: + """Add symbol directly to the global symbol table.""" raise NotImplementedError @abstractmethod diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 0f62a4aa8b1a2..5b6d9b16273ef 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -108,7 +108,10 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None: self.seen_aliases.discard(t) def visit_tuple_type(self, t: TupleType) -> None: - t.items = flatten_nested_tuples(t.items) + # Unfortunately, universal normalization of tuples is not possible in presence of + # recursive aliases, see testNoCrashOnNonNormalRecursiveTuple for an example. + # TODO: update the places where we handle tuples to always expect non-normal ones. + t.items = flatten_nested_tuples(t.items, handle_recursive=False) for i, it in enumerate(t.items): if self.check_non_paramspec(it, "tuple", t): t.items[i] = AnyType(TypeOfAny.from_error) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 3655e4c89dd4b..b1ba9f6c3abec 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -3,13 +3,12 @@ from __future__ import annotations from collections.abc import Collection -from typing import Final +from typing import Final, NamedTuple from mypy import errorcodes as codes, message_registry from mypy.errorcodes import ErrorCode from mypy.expandtype import expand_type from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type -from mypy.message_registry import TYPEDDICT_OVERRIDE_MERGE from mypy.messages import MessageBuilder from mypy.nodes import ( ARG_NAMED, @@ -31,8 +30,11 @@ TempNode, TupleExpr, TypeAlias, + TypedDictData, TypedDictExpr, + TypedDictFieldSource, TypeInfo, + inline_base, ) from mypy.options import Options from mypy.semanal_shared import ( @@ -59,6 +61,14 @@ ) +class FieldSource(NamedTuple): + field_type: Type + is_readonly: bool + is_required: bool + base: TypeInfo | None + ctx: Context + + class TypedDictAnalyzer: def __init__( self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder @@ -101,22 +111,32 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N if isinstance(defn.analyzed, TypedDictExpr): existing_info = defn.analyzed.info - field_types: dict[str, Type] | None + is_closed: bool | None = None + if "closed" in defn.keywords: + is_closed = require_bool_literal_argument( + self.api, defn.keywords["closed"], "closed", False + ) + if ( len(defn.base_type_exprs) == 1 and isinstance(defn.base_type_exprs[0], RefExpr) and defn.base_type_exprs[0].fullname in TPDICT_NAMES ): # Building a new TypedDict - field_types, statements, required_keys, readonly_keys = ( - self.analyze_typeddict_classdef_fields(defn) - ) - if field_types is None: + field_sources, statements = self.analyze_typeddict_classdef_fields(defn) + if field_sources is None: return True, None # Defer - if self.api.is_func_scope() and "@" not in defn.name: - defn.name += "@" + str(defn.line) + field_types = {key: source.field_type for (key, source) in field_sources.items()} + required_keys = {key for (key, source) in field_sources.items() if source.is_required} + readonly_keys = {key for (key, source) in field_sources.items() if source.is_readonly} info = self.build_typeddict_typeinfo( - defn.name, field_types, required_keys, readonly_keys, defn.line, existing_info + defn.name, + field_types, + required_keys, + readonly_keys, + is_closed or False, + defn.line, + existing_info, ) defn.analyzed = TypedDictExpr(info) defn.analyzed.line = defn.line @@ -127,8 +147,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N # Extending/merging existing TypedDicts typeddict_bases: list[Expression] = [] typeddict_bases_set = set() - for expr in defn.base_type_exprs: - ok, maybe_type_info, _ = self.check_typeddict(expr, None, False) + for i, expr in enumerate(defn.base_type_exprs): + ok, maybe_type_info, _ = self.check_typeddict(expr, inline_base(defn.name, i)) if ok and maybe_type_info is not None: # expr is a CallExpr info = maybe_type_info @@ -154,24 +174,27 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N else: self.fail("All bases of a new TypedDict must be TypedDict types", defn) - field_types = {} - required_keys = set() - readonly_keys = set() - # Iterate over bases in reverse order so that leftmost base class' keys take precedence - for base in reversed(typeddict_bases): - self.add_keys_and_types_from_base( - base, field_types, required_keys, readonly_keys, defn - ) - new_field_types, new_statements, new_required_keys, new_readonly_keys = ( - self.analyze_typeddict_classdef_fields(defn, oldfields=field_types) - ) - if new_field_types is None: + bases_info: list[tuple[TypeInfo, dict[str, Type]]] = [] + for base in typeddict_bases: + base_info = self.fetch_keys_and_types_from_base(base, defn) + if base_info is not None: + bases_info.append(base_info) + new_field_sources, new_statements = self.analyze_typeddict_classdef_fields(defn) + if new_field_sources is None: return True, None # Defer - field_types.update(new_field_types) - required_keys.update(new_required_keys) - readonly_keys.update(new_readonly_keys) + field_types, required_keys, readonly_keys, is_closed, field_sources = ( + self.resolve_field_inheritance(bases_info, new_field_sources, is_closed, defn) + ) + typeddict_data = TypedDictData(True, bases_info, field_sources) info = self.build_typeddict_typeinfo( - defn.name, field_types, required_keys, readonly_keys, defn.line, existing_info + defn.name, + field_types, + required_keys, + readonly_keys, + is_closed, + defn.line, + existing_info, + typeddict_data=typeddict_data, ) defn.analyzed = TypedDictExpr(info) defn.analyzed.line = defn.line @@ -179,20 +202,15 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> tuple[bool, TypeInfo | N defn.defs.body = new_statements return True, info - def add_keys_and_types_from_base( - self, - base: Expression, - field_types: dict[str, Type], - required_keys: set[str], - readonly_keys: set[str], - ctx: Context, - ) -> None: + def fetch_keys_and_types_from_base( + self, base: Expression, ctx: Context + ) -> tuple[TypeInfo, dict[str, Type]] | None: info = self._parse_typeddict_base(base, ctx) base_args: list[Type] = [] if isinstance(base, IndexExpr): args = self.analyze_base_args(base, ctx) if args is None: - return + return None base_args = args assert info.typeddict_type is not None @@ -211,13 +229,167 @@ def add_keys_and_types_from_base( with state.strict_optional_set(self.options.strict_optional): valid_items = self.map_items_to_base(valid_items, tvars, base_args) - for key in base_items: - if key in field_types: - self.fail(TYPEDDICT_OVERRIDE_MERGE.format(key), ctx) - field_types.update(valid_items) - required_keys.update(base_typed_dict.required_keys) - readonly_keys.update(base_typed_dict.readonly_keys) + return info, valid_items + + def field_sources_in_reverse_order( + self, + bases: list[tuple[TypeInfo, dict[str, Type]]], + child_field_sources: dict[str, FieldSource], + ctx: Context, + ) -> dict[str, list[FieldSource]]: + """Find all keys in bases and child, mapping them to a list of sources. + + Iterate bases in reverse order to preserve key ordering for display. + """ + result: dict[str, list[FieldSource]] = {} + for base_info, base_fields in reversed(bases): + assert base_info.typeddict_type is not None + for field_name, field_type in base_fields.items(): + source = FieldSource( + field_type=field_type, + is_readonly=field_name in base_info.typeddict_type.readonly_keys, + is_required=field_name in base_info.typeddict_type.required_keys, + base=base_info, + ctx=ctx, + ) + result.setdefault(field_name, []).append(source) + for field_name, source in child_field_sources.items(): + result.setdefault(field_name, []).append(source) + return result + + def primary_source(self, sources: list[FieldSource]) -> FieldSource: + """Select a primary source from a reverse-ordered list of sources. + + The primary source will be the last in the list, skipping readonly + base class sources unless they are the only available option. + """ + if not sources[-1].base: + return sources[-1] + mutable_sources = (s for s in reversed(sources) if not s.is_readonly) + return next(mutable_sources, sources[-1]) + + def verify_requiredness_compatibility( + self, + field_name: str, + source: FieldSource, + is_required: bool, + primary_source_base: TypeInfo | None, + ctx: Context, + ) -> None: + """Verify requiredness compatibility of the final child type field with a base class source.""" + assert source.base + if source.is_required and not is_required: + if primary_source_base is None: + self.fail( + f'Field "{field_name}" is required in base class "{source.base.name}"', ctx + ) + else: + self.fail( + f'Field "{field_name}" is required in base class "{source.base.name}" but can ' + f'be deleted in base class "{primary_source_base.name}"', + ctx, + ) + elif not source.is_required and not source.is_readonly and is_required: + if primary_source_base is None: + self.fail( + f'Field "{field_name}" can be deleted in base class "{source.base.name}"', ctx + ) + else: + self.fail( + f'Field "{field_name}" is required in base class "{primary_source_base.name}" ' + f'but can be deleted in base class "{source.base.name}"', + ctx, + ) + + def verify_field_against_closed_bases( + self, + field_name: str, + closed_bases: Collection[tuple[TypeInfo, Collection[str]]], + primary_source_base: TypeInfo | None, + ctx: Context, + ) -> None: + for closed_base_type, closed_base_fields in closed_bases: + if field_name in closed_base_fields: + continue + + if primary_source_base: + self.fail( + f'Cannot extend closed base class "{closed_base_type.name}" with field ' + f'"{field_name}" from base class "{primary_source_base.name}"', + ctx, + ) + else: + self.fail( + f'Cannot extend closed base class "{closed_base_type.name}" with new field ' + f'"{field_name}"', + ctx, + ) + + def resolve_field_inheritance( + self, + bases: list[tuple[TypeInfo, dict[str, Type]]], + child_field_sources: dict[str, FieldSource], + child_is_closed: bool | None, + ctx: Context, + ) -> tuple[dict[str, Type], set[str], set[str], bool, dict[str, TypedDictFieldSource]]: + """Determine field types, requiredness, readonlyness, and closedness.""" + field_sources = self.field_sources_in_reverse_order(bases, child_field_sources, ctx) + field_types: dict[str, Type] = {} + chosen_sources: dict[str, TypedDictFieldSource] = {} + required_keys: set[str] = set() + readonly_keys: set[str] = set() + closed_bases = [ + (base_info, base_fields.keys()) + for (base_info, base_fields) in bases + if base_info.typeddict_type and base_info.typeddict_type.is_closed + ] + + if child_is_closed is False and closed_bases: + for base_info, _ in closed_bases: + self.fail( + f"Open TypedDict class cannot subclass closed TypedDict class " + f'"{base_info.name}"', + ctx, + ) + + for field_name, sources in field_sources.items(): + primary_source = self.primary_source(sources) + # If a read-only field is only defined in base classes, joining the types + # is unlikely to produce a tight enough result. We could check all the + # candidates from the base classes, but it would be O(n^2) complexity + # to find out which is a supertype of all the others. Instead, use the + # first definition we encounter, and let the user provide the correct + # definition in the subclass if this fails. + field_types[field_name] = primary_source.field_type + chosen_sources[field_name] = TypedDictFieldSource( + base=primary_source.base, ctx=primary_source.ctx + ) + + if primary_source.is_readonly: + # If the primary source is readonly, all sources are readonly + is_readonly = True + is_required = any(source.is_required for source in sources) + else: + is_readonly = False + is_required = primary_source.is_required + + if is_required: + required_keys.add(field_name) + if is_readonly: + readonly_keys.add(field_name) + + for source in sources: + if source is not primary_source: + self.verify_requiredness_compatibility( + field_name, source, is_required, primary_source.base, primary_source.ctx + ) + self.verify_field_against_closed_bases( + field_name, closed_bases, primary_source.base, primary_source.ctx + ) + + is_closed = bool(closed_bases) if child_is_closed is None else child_is_closed + return field_types, required_keys, readonly_keys, is_closed, chosen_sources def _parse_typeddict_base(self, base: Expression, ctx: Context) -> TypeInfo: if isinstance(base, RefExpr): @@ -288,22 +460,19 @@ def map_items_to_base( return mapped_items def analyze_typeddict_classdef_fields( - self, defn: ClassDef, oldfields: Collection[str] | None = None - ) -> tuple[dict[str, Type] | None, list[Statement], set[str], set[str]]: + self, defn: ClassDef + ) -> tuple[dict[str, FieldSource] | None, list[Statement]]: """Analyze fields defined in a TypedDict class definition. This doesn't consider inherited fields (if any). Also consider totality, if given. Return tuple with these items: - * Dict of key -> type (or None if found an incomplete reference -> deferral) + * Dict of key -> field source (or None if found an incomplete reference -> deferral) * List of statements from defn.defs.body that are legally allowed to be a part of a TypedDict definition - * Set of required keys """ - fields: dict[str, Type] = {} - readonly_keys = set[str]() - required_keys = set[str]() + fields: dict[str, FieldSource] = {} statements: list[Statement] = [] total: bool | None = True @@ -313,6 +482,8 @@ def analyze_typeddict_classdef_fields( self.api, defn.keywords["total"], "total", True ) continue + elif key == "closed": + continue for_function = ' for "__init_subclass__" of "TypedDict"' self.msg.unexpected_keyword_argument_for_function(for_function, key, defn) @@ -333,8 +504,6 @@ def analyze_typeddict_classdef_fields( self.fail(TPDICT_CLASS_ERROR, stmt) else: name = stmt.lvalues[0].name - if name in (oldfields or []): - self.fail(f'Overwriting TypedDict field "{name}" while extending', stmt) if name in fields: self.fail(f'Duplicate TypedDict key "{name}"', stmt) continue @@ -353,18 +522,19 @@ def analyze_typeddict_classdef_fields( prohibit_special_class_field_types="TypedDict", ) if analyzed is None: - return None, [], set(), set() # Need to defer + return None, [] # Need to defer field_type = analyzed if not has_placeholder(analyzed): stmt.type = self.extract_meta_info(analyzed, stmt)[0] field_type, required, readonly = self.extract_meta_info(field_type) - fields[name] = field_type - - if (total or required is True) and required is not False: - required_keys.add(name) - if readonly: - readonly_keys.add(name) + fields[name] = FieldSource( + field_type=field_type, + is_required=(total or required is True) and required is not False, + is_readonly=readonly, + base=None, + ctx=stmt, + ) # ...despite possible minor failures that allow further analysis. if stmt.type is None or hasattr(stmt, "new_syntax") and not stmt.new_syntax: @@ -373,7 +543,7 @@ def analyze_typeddict_classdef_fields( # x: int assigns rvalue to TempNode(AnyType()) self.fail("Right hand side values are not supported in TypedDict", stmt) - return fields, statements, required_keys, readonly_keys + return fields, statements def extract_meta_info( self, typ: Type, context: Context | None = None @@ -406,12 +576,13 @@ def extract_meta_info( return typ, is_required, readonly def check_typeddict( - self, node: Expression, var_name: str | None, is_func_scope: bool + self, node: Expression, name: str ) -> tuple[bool, TypeInfo | None, list[TypeVarLikeType]]: """Check if a call defines a TypedDict. - The optional var_name argument is the name of the variable to - which this is assigned, if any. + The name argument is the name of the variable to which this is assigned. + For an inlined base class this is a unique name generated from class name + base number. Return a pair (is it a typed dict, corresponding TypeInfo). @@ -433,40 +604,30 @@ def check_typeddict( # This is a valid typed dict, but some type is not ready. # The caller should defer this until next iteration. return True, None, [] - name, items, types, total, tvar_defs, ok = res + typename, items, wrapped_types, total, closed, tvar_defs, ok = res if not ok: # Error. Construct dummy return value. - if var_name: - name = var_name - if is_func_scope: - name += "@" + str(call.line) - else: - name = var_name = "TypedDict@" + str(call.line) - info = self.build_typeddict_typeinfo(name, {}, set(), set(), call.line, None) + info = self.build_typeddict_typeinfo(name, {}, set(), set(), False, call.line, None) else: - if var_name is not None and name != var_name: + if "@" not in name and name != typename: self.fail( 'First argument "{}" to TypedDict() does not match variable name "{}"'.format( - name, var_name + typename, name ), node, code=codes.NAME_MATCH, ) - if name != var_name or is_func_scope: - # Give it a unique name derived from the line number. - name += "@" + str(call.line) - required_keys = { - field - for (field, t) in zip(items, types) - if (total or (isinstance(t, RequiredType) and t.required)) - and not (isinstance(t, RequiredType) and not t.required) - } - readonly_keys = { - field for (field, t) in zip(items, types) if isinstance(t, ReadOnlyType) - } - types = [ # unwrap Required[T] or ReadOnly[T] to just T - t.item if isinstance(t, (RequiredType, ReadOnlyType)) else t for t in types - ] + # Unwrap special forms (Required/NotRequired/ReadOnly) + types: list[Type] = [] + required_keys: set[str] = set() + readonly_keys: set[str] = set() + for field, t in zip(items, wrapped_types): + unwrapped_type, is_required, is_readonly = self.extract_meta_info(t, node) + types.append(unwrapped_type) + if is_required is True or (is_required is None and total): + required_keys.add(field) + if is_readonly: + readonly_keys.add(field) # Perform various validations after unwrapping. for t in types: @@ -481,45 +642,54 @@ def check_typeddict( existing_info = None if isinstance(node.analyzed, TypedDictExpr): existing_info = node.analyzed.info + info = self.build_typeddict_typeinfo( name, dict(zip(items, types)), required_keys, readonly_keys, + closed, call.line, existing_info, ) info.line = node.line # Store generated TypeInfo under both names, see semanal_namedtuple for more details. - if name != var_name or is_func_scope: - self.api.add_symbol_skip_local(name, info) - if var_name: - self.api.add_symbol(var_name, info, node) + self.api.add_symbol(name, info, node) + if self.api.is_nested_within_func_scope(): + self.api.add_global_symbol(name, node, info) call.analyzed = TypedDictExpr(info) call.analyzed.set_line(call) return True, info, tvar_defs def parse_typeddict_args( self, call: CallExpr - ) -> tuple[str, list[str], list[Type], bool, list[TypeVarLikeType], bool] | None: + ) -> tuple[str, list[str], list[Type], bool, bool, list[TypeVarLikeType], bool] | None: """Parse typed dict call expression. - Return names, types, totality, was there an error during parsing. + Return names, types, totality, open/closed, was there an error during parsing. If some type is not ready, return None. """ # TODO: Share code with check_argument_count in checkexpr.py? args = call.args if len(args) < 2: return self.fail_typeddict_arg("Too few arguments for TypedDict()", call) - if len(args) > 3: + if len(args) > 4: return self.fail_typeddict_arg("Too many arguments for TypedDict()", call) - # TODO: Support keyword arguments - if call.arg_kinds not in ([ARG_POS, ARG_POS], [ARG_POS, ARG_POS, ARG_NAMED]): + if call.arg_kinds[:2] != [ARG_POS, ARG_POS] or any( + arg_kind != ARG_NAMED for arg_kind in call.arg_kinds[2:] + ): return self.fail_typeddict_arg("Unexpected arguments to TypedDict()", call) - if len(args) == 3 and call.arg_names[2] != "total": - return self.fail_typeddict_arg( - f'Unexpected keyword argument "{call.arg_names[2]}" for "TypedDict"', call - ) + seen_arg_names = set() + for arg_name in call.arg_names[2:]: + if arg_name not in ("total", "closed"): + return self.fail_typeddict_arg( + f'Unexpected keyword argument "{arg_name}" for "TypedDict"', call + ) + if arg_name in seen_arg_names: + return self.fail_typeddict_arg( + f'Repeated keyword argument "{arg_name}" for "TypedDict"', call + ) + seen_arg_names.add(arg_name) if not isinstance(args[0], StrExpr): return self.fail_typeddict_arg( "TypedDict() expects a string literal as the first argument", call @@ -529,10 +699,16 @@ def parse_typeddict_args( "TypedDict() expects a dictionary literal as the second argument", call ) total: bool | None = True - if len(args) == 3: - total = require_bool_literal_argument(self.api, call.args[2], "total") - if total is None: - return "", [], [], True, [], False + closed: bool = False + for arg_name, arg in zip(call.arg_names[2:], call.args[2:]): + assert arg_name + value = require_bool_literal_argument(self.api, arg, arg_name) + if value is None: + return "", [], [], True, False, [], False + if arg_name == "closed": + closed = value + else: + total = value dictexpr = args[1] tvar_defs = self.api.get_and_bind_all_tvars([t for k, t in dictexpr.items]) res = self.parse_typeddict_fields_with_types(dictexpr.items) @@ -541,7 +717,7 @@ def parse_typeddict_args( return None items, types, ok = res assert total is not None - return args[0].value, items, types, total, tvar_defs, ok + return args[0].value, items, types, total, closed, tvar_defs, ok def parse_typeddict_fields_with_types( self, dict_items: list[tuple[Expression | None, Expression]] @@ -585,9 +761,9 @@ def parse_typeddict_fields_with_types( def fail_typeddict_arg( self, message: str, context: Context - ) -> tuple[str, list[str], list[Type], bool, list[TypeVarLikeType], bool]: + ) -> tuple[str, list[str], list[Type], bool, bool, list[TypeVarLikeType], bool]: self.fail(message, context) - return "", [], [], True, [], False + return "", [], [], True, False, [], False def build_typeddict_typeinfo( self, @@ -595,8 +771,10 @@ def build_typeddict_typeinfo( item_types: dict[str, Type], required_keys: set[str], readonly_keys: set[str], + is_closed: bool, line: int, existing_info: TypeInfo | None, + typeddict_data: TypedDictData | None = None, ) -> TypeInfo: # Prefer typing then typing_extensions if available. fallback = ( @@ -606,12 +784,29 @@ def build_typeddict_typeinfo( ) assert fallback is not None info = existing_info or self.api.basic_new_typeinfo(name, fallback, line) - typeddict_type = TypedDictType(item_types, required_keys, readonly_keys, fallback) - if has_placeholder(typeddict_type): + typeddict_type = TypedDictType( + item_types, required_keys, readonly_keys, fallback, is_closed=is_closed + ) + any_placeholder = has_placeholder(typeddict_type) + if typeddict_data: + for _, base_fields in typeddict_data.bases: + for field_type in base_fields.values(): + if has_placeholder(field_type): + any_placeholder = True + else: + typeddict_data = TypedDictData(True, [], {}) + if any_placeholder: + typeddict_data.ready = False + force_progress = ( + typeddict_type != info.typeddict_type + or info.typeddict_data is None + or typeddict_data.bases != info.typeddict_data.bases + ) self.api.process_placeholder( - None, "TypedDict item", info, force_progress=typeddict_type != info.typeddict_type + None, "TypedDict item", info, force_progress=force_progress ) info.update_typeddict_type(typeddict_type) + info.typeddict_data = typeddict_data return info # Helpers diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 9bbc3077ec512..7b7a3d6c84564 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -470,6 +470,9 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem: typ.is_ellipsis_args, snapshot_types(typ.variables), typ.is_bound, + snapshot_optional_type(typ.type_guard), + snapshot_optional_type(typ.type_is), + snapshot_optional_type(typ.instance_type), ) def normalize_callable_variables(self, typ: CallableType) -> CallableType: @@ -497,7 +500,7 @@ def visit_typeddict_type(self, typ: TypedDictType) -> SnapshotItem: items = tuple((key, snapshot_type(item_type)) for key, item_type in typ.items.items()) required = tuple(sorted(typ.required_keys)) readonly = tuple(sorted(typ.readonly_keys)) - return ("TypedDictType", items, required, readonly) + return ("TypedDictType", items, required, readonly, typ.is_closed) def visit_literal_type(self, typ: LiteralType) -> SnapshotItem: return ("LiteralType", snapshot_type(typ.fallback), typ.value) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index aaf388b6665d6..5b723711405a1 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -340,6 +340,8 @@ def visit_var(self, node: Var) -> None: super().visit_var(node) def visit_type_alias(self, node: TypeAlias) -> None: + # Updating alias target can invalidate its recursive status. + node._is_recursive = None self.fixup_type(node.target) for v in node.alias_tvars: self.fixup_type(v) @@ -452,6 +454,12 @@ def visit_callable_type(self, typ: CallableType) -> None: # Fallback can be None for callable types that haven't been semantically analyzed. if typ.fallback is not None: typ.fallback.accept(self) + if typ.type_guard is not None: + typ.type_guard.accept(self) + if typ.type_is is not None: + typ.type_is.accept(self) + if typ.instance_type is not None: + typ.instance_type.accept(self) for tv in typ.variables: if isinstance(tv, TypeVarType): tv.upper_bound.accept(self) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index ba622329665ea..29cd8ac8b4635 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -671,6 +671,13 @@ def visit_member_expr(self, e: MemberExpr) -> None: if e.kind is not None: # Reference to a module attribute self.process_global_ref_expr(e) + if isinstance(e.expr, RefExpr) and isinstance(e.expr.node, MypyFile): + # Also depend on the name as accessed through this module. The + # fullname above may point to the original definition (e.g. via + # a re-export using "from ... import *"), but we must also + # recheck if the name is removed from the accessed module's + # namespace. + self.add_dependency(make_trigger(e.expr.node.fullname + "." + e.name)) else: # Reference to a non-module (or missing) attribute if e.expr not in self.type_map: @@ -1003,6 +1010,12 @@ def visit_callable_type(self, typ: CallableType) -> list[str]: for arg in typ.arg_types: triggers.extend(self.get_type_triggers(arg)) triggers.extend(self.get_type_triggers(typ.ret_type)) + if typ.type_guard is not None: + triggers.extend(self.get_type_triggers(typ.type_guard)) + if typ.type_is is not None: + triggers.extend(self.get_type_triggers(typ.type_is)) + if typ.instance_type is not None: + triggers.extend(self.get_type_triggers(typ.instance_type)) # fallback is a metaclass type for class objects, and is # processed separately. return triggers diff --git a/mypy/server/update.py b/mypy/server/update.py index 64ce0fb5d3f8a..cb7ff60e14cba 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -783,31 +783,37 @@ def calculate_active_triggers( else: snapshot2 = snapshot_symbol_table(id, new.names) diff = compare_symbol_table_snapshots(id, snapshot1, snapshot2) - package_nesting_level = id.count(".") - for item in diff.copy(): - if item.count(".") <= package_nesting_level + 1 and item.split(".")[-1] not in ( - "__builtins__", - "__file__", - "__name__", - "__package__", - "__doc__", - ): - # Activate catch-all wildcard trigger for top-level module changes (used for - # "from m import *"). This also gets triggered by changes to module-private - # entries, but as these unneeded dependencies only result in extra processing, - # it's a minor problem. - # - # TODO: Some __* names cause mistriggers. Fix the underlying issue instead of - # special casing them here. - diff.add(id + WILDCARD_TAG) - if item.count(".") > package_nesting_level + 1: - # These are for changes within classes, used by protocols. - diff.add(item.rsplit(".", 1)[0] + WILDCARD_TAG) - + diff |= wildcard_triggers_for_changes(id, diff) names |= diff return {make_trigger(name) for name in names} +def wildcard_triggers_for_changes(module_id: str, diff: set[str]) -> set[str]: + """Return catch-all wildcard triggers activated by a set of changed names.""" + result: set[str] = set() + package_nesting_level = module_id.count(".") + for item in diff: + if item.count(".") <= package_nesting_level + 1 and item.split(".")[-1] not in ( + "__builtins__", + "__file__", + "__name__", + "__package__", + "__doc__", + ): + # Activate catch-all wildcard trigger for top-level module changes (used for + # "from m import *"). This also gets triggered by changes to module-private + # entries, but as these unneeded dependencies only result in extra processing, + # it's a minor problem. + # + # TODO: Some __* names cause mistriggers. Fix the underlying issue instead of + # special casing them here. + result.add(module_id + WILDCARD_TAG) + if item.count(".") > package_nesting_level + 1: + # These are for changes within classes, used by protocols. + result.add(item.rsplit(".", 1)[0] + WILDCARD_TAG) + return result + + def replace_modules_with_new_variants( manager: BuildManager, graph: dict[str, State], @@ -1044,6 +1050,7 @@ def key(node: FineGrainedDeferredNode) -> int: changed = compare_symbol_table_snapshots( file_node.fullname, old_symbols_snapshot, new_symbols_snapshot ) + changed |= wildcard_triggers_for_changes(module_id, changed) new_triggered = {make_trigger(name) for name in changed} # Dependencies may have changed. diff --git a/mypy/solve.py b/mypy/solve.py index e3709106996cd..4a5eec47ca60d 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -17,6 +17,7 @@ AnyType, Instance, NoneType, + Overloaded, ParamSpecType, ProperType, TupleType, @@ -253,6 +254,9 @@ def _join_sorted_key(t: Type) -> int: return -2 if isinstance(t, NoneType): return -1 + + if isinstance(t, Overloaded): + return 1 return 0 diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 9c682ba4b8201..fe90c3d256b7a 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -1094,9 +1094,13 @@ def process_typeddict(self, lvalue: NameExpr, rvalue: CallExpr) -> None: if not isinstance(rvalue.args[0], StrExpr): self.annotate_as_incomplete(lvalue) return + if len(rvalue.args) > 2 and rvalue.arg_kinds[2] != ARG_NAMED: + self.annotate_as_incomplete(lvalue) + return items: list[tuple[str, Expression]] = [] total: Expression | None = None + closed: Expression | None = None if len(rvalue.args) > 1 and rvalue.arg_kinds[1] == ARG_POS: if not isinstance(rvalue.args[1], DictExpr): self.annotate_as_incomplete(lvalue) @@ -1106,11 +1110,14 @@ def process_typeddict(self, lvalue: NameExpr, rvalue: CallExpr) -> None: self.annotate_as_incomplete(lvalue) return items.append((attr_name.value, attr_type)) - if len(rvalue.args) > 2: - if rvalue.arg_kinds[2] != ARG_NAMED or rvalue.arg_names[2] != "total": + for arg_name, arg in zip(rvalue.arg_names[2:], rvalue.args[2:]): + if arg_name == "total": + total = arg + elif arg_name == "closed": + closed = arg + else: self.annotate_as_incomplete(lvalue) return - total = rvalue.args[2] else: for arg_name, arg in zip(rvalue.arg_names[1:], rvalue.args[1:]): if not isinstance(arg_name, str): @@ -1130,6 +1137,8 @@ def process_typeddict(self, lvalue: NameExpr, rvalue: CallExpr) -> None: # TODO: Add support for generic TypedDicts. Requires `Generic` as base class. if total is not None: bases += f", total={total.accept(p)}" + if closed is not None: + bases += f", closed={closed.accept(p)}" class_def = f"{self._indent}class {lvalue.name}({bases}):" if len(items) == 0: self.add(f"{class_def} ...\n") @@ -1745,13 +1754,7 @@ def parse_source_file(mod: StubSource, mypy_options: MypyOptions) -> None: source = mypy.util.decode_python_encoding(data) errors = Errors(mypy_options) mod.ast = mypy.parse.parse( - source, - fnam=mod.path, - module=mod.module, - errors=errors, - options=mypy_options, - file_exists=True, - eager=True, + source, fnam=mod.path, module=mod.module, errors=errors, options=mypy_options, eager=True ) mod.ast._fullname = mod.module if errors.is_blockers(): diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 5733797326e88..2d97dd534d9f5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -762,17 +762,15 @@ def visit_callable_type(self, left: CallableType) -> bool: if is_protocol_implementation(left.fallback, right, skip=["__call__"]): return True if right.type.is_protocol and left.is_type_obj(): - ret_type = get_proper_type(left.ret_type) - if isinstance(ret_type, TupleType): - ret_type = mypy.typeops.tuple_fallback(ret_type) - if isinstance(ret_type, Instance) and is_protocol_implementation( - ret_type, right, proper_subtype=self.proper_subtype, class_obj=True + instance_type = left.get_instance_type(force_fallback=True) + if isinstance(instance_type, Instance) and is_protocol_implementation( + instance_type, right, proper_subtype=self.proper_subtype, class_obj=True ): return True return self._is_subtype(left.fallback, right) elif isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature. - return left.is_type_obj() and self._is_subtype(left.ret_type, right.item) + return left.is_type_obj() and self._is_subtype(left.get_instance_type(), right.item) else: return False @@ -811,6 +809,12 @@ def visit_tuple_type(self, left: TupleType) -> bool: mypy.typeops.tuple_fallback(left), right ): return True + elif right.type.is_protocol and is_protocol_implementation( + left, right, proper_subtype=self.proper_subtype + ): + # Special-case protocols to get precise binding of self type for + # custom tuple types like NamedTuples. + return True return False elif isinstance(right, TupleType): # If right has a variadic unpack this needs special handling. If there is a TypeVarTuple @@ -934,46 +938,53 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool: elif isinstance(right, TypedDictType): if left == right: return True # Fast path - if not left.names_are_wider_than(right): + + # A closed type must remain closed + if right.is_closed and not left.is_closed: return False - for name, l, r in left.zip(right): - # TODO: should we pass on the full subtype_context here and below? - right_readonly = name in right.readonly_keys - if not right_readonly: + + # Perform fast key-based checks before recursing into value types + for name, l, r in left.zipall(right): + # Required keys must remain required + if r.required and not l.required: + return False + # Mutable keys must remain mutable + if r.mutable and not l.mutable: + return False + # Mutable optional keys must also remain optional, + # to retain the ability to delete them + if r.mutable and not r.required and l.required: + return False + + for name, l, r in left.zipall(right): + if r.mutable: + # None will only be used for missing ReadOnly[object] keys + assert r.typ is not None + # Guaranteed by "mutable keys must remain mutable" check + assert l.typ is not None + + # TODO: should we pass on the full subtype_context here and below? if self.proper_subtype: - check = is_same_type(l, r) + check = is_same_type(l.typ, r.typ) else: check = is_equivalent( - l, - r, + l.typ, + r.typ, ignore_type_params=self.subtype_context.ignore_type_params, options=self.options, ) else: # Read-only items behave covariantly - check = self._is_subtype(l, r) + if r.typ is None: + check = True + elif l.typ is None: + # Keys cannot be dropped in an open subtype + # as this would implicitly widen the type constraint + check = False + else: + check = self._is_subtype(l.typ, r.typ) if not check: return False - # Non-required key is not compatible with a required key since - # indexing may fail unexpectedly if a required key is missing. - # Required key is not compatible with a non-read-only non-required - # key since the prior doesn't support 'del' but the latter should - # support it. - # Required key is compatible with a read-only non-required key. - required_differ = (name in left.required_keys) != (name in right.required_keys) - if not right_readonly and required_differ: - return False - # Readonly fields check: - # - # A = TypedDict('A', {'x': ReadOnly[int]}) - # B = TypedDict('B', {'x': int}) - # def reset_x(b: B) -> None: - # b['x'] = 0 - # - # So, `A` cannot be a subtype of `B`, while `B` can be a subtype of `A`, - # because you can use `B` everywhere you use `A`, but not the other way around. - if name in left.readonly_keys and name not in right.readonly_keys: - return False # (NOTE: Fallbacks don't matter.) return True else: @@ -1148,6 +1159,21 @@ def visit_type_type(self, left: TypeType) -> bool: # We can't accept `Type[X]` as a *proper* subtype of Callable[P, X] # since this will break transitivity of subtyping. return False + item = left.item + # Note: self-types are special since returning `Self` is more precise + # than its upper bound, so we don't unwrap TypeVar to its bound here. + if isinstance(item, TupleType): + item = mypy.typeops.tuple_fallback(item) + if isinstance(item, Instance): + constructor = mypy.typeops.type_object_type(item.type) + constructor = expand_type_by_instance(constructor, item) + if isinstance(constructor, CallableType): + # Only consider return type to match historic behavior (see below). + return self._is_subtype(constructor.ret_type, right.ret_type) + elif isinstance(constructor, Overloaded): + return all( + self._is_subtype(c.ret_type, right.ret_type) for c in constructor.items + ) # This is unsound, we don't check the __init__ signature. return self._is_subtype(left.item, right.ret_type) @@ -1185,7 +1211,7 @@ def pop_on_exit(stack: list[tuple[T, T]], left: T, right: T) -> Iterator[None]: def is_protocol_implementation( - left: Instance, + left: Instance | TupleType, right: Instance, proper_subtype: bool = False, class_obj: bool = False, @@ -1212,6 +1238,11 @@ def f(self) -> A: ... assert right.type.is_protocol if skip is None: skip = [] + # Preserve original left type for precise self-type binding. Only tuple types are + # supported for now. + original_left = left + if isinstance(left, TupleType): + left = mypy.typeops.tuple_fallback(left) # We need to record this check to generate protocol fine-grained dependencies. type_state.record_protocol_subtype_check(left.type, right.type) # nominal subtyping currently ignores '__init__' and '__new__' signatures @@ -1234,10 +1265,10 @@ def f(self) -> A: ... ignore_names = member != "__call__" # __call__ can be passed kwargs # The third argument below indicates to what self type is bound. # We always bind self to the subtype. (Similarly to nominal types). - supertype = find_member(member, right, left) + supertype = find_member(member, right, original_left) assert supertype is not None - subtype = get_protocol_member(left, member, class_obj) + subtype = get_protocol_member(left, original_left, member, class_obj) # Useful for debugging: # print(member, 'of', left, 'has type', subtype) # print(member, 'of', right, 'has type', supertype) @@ -1264,9 +1295,11 @@ def f(self) -> A: ... if IS_SETTABLE in superflags: # Check opposite direction for settable attributes. if IS_EXPLICIT_SETTER in superflags: - supertype = find_member(member, right, left, is_lvalue=True) + supertype = find_member(member, right, original_left, is_lvalue=True) if IS_EXPLICIT_SETTER in subflags: - subtype = get_protocol_member(left, member, class_obj, is_lvalue=True) + subtype = get_protocol_member( + left, original_left, member, class_obj, is_lvalue=True + ) # At this point we know attribute is present on subtype, otherwise we # would return False above. assert supertype is not None and subtype is not None @@ -1305,7 +1338,7 @@ def f(self) -> A: ... def get_protocol_member( - left: Instance, member: str, class_obj: bool, is_lvalue: bool = False + left: Instance, original_left: Type, member: str, class_obj: bool, is_lvalue: bool = False ) -> Type | None: if member == "__call__" and class_obj: # Special case: class objects always have __call__ that is just the constructor. @@ -1316,7 +1349,7 @@ def get_protocol_member( # if constructor signature didn't match, this can cause many false negatives. return None - subtype = find_member(member, left, left, class_obj=class_obj, is_lvalue=is_lvalue) + subtype = find_member(member, left, original_left, class_obj=class_obj, is_lvalue=is_lvalue) if isinstance(subtype, PartialType): subtype = ( NoneType() @@ -2245,7 +2278,7 @@ def infer_variance(info: TypeInfo, i: int) -> bool: self_type = fill_typevars(info) for member in all_non_object_members(info): # __mypy-replace is an implementation detail of the dataclass plugin - if member in ("__init__", "__new__", "__mypy-replace"): + if member in {"__init__", "__new__", "__replace__", "__mypy-replace"}: continue if isinstance(self_type, TupleType): diff --git a/mypy/test/test_nativeparse.py b/mypy/test/test_nativeparse.py index b50da5f5d02c7..1e63f4abc2991 100644 --- a/mypy/test/test_nativeparse.py +++ b/mypy/test/test_nativeparse.py @@ -52,7 +52,11 @@ class NativeParserSuite(DataSuite): required_out_section = True base_path = "." - files = ["native-parser.test"] if has_nativeparse else [] + files = ( + ["native-parser.test", "native-parser-python311.test", "native-parser-python312.test"] + if has_nativeparse + else [] + ) def run_case(self, testcase: DataDrivenTestCase) -> None: test_parser(testcase) @@ -77,6 +81,8 @@ def test_parser(testcase: DataDrivenTestCase) -> None: if testcase.file.endswith("python310.test"): options.python_version = (3, 10) + elif testcase.file.endswith("python311.test"): + options.python_version = (3, 11) elif testcase.file.endswith("python312.test"): options.python_version = (3, 12) elif testcase.file.endswith("python313.test"): @@ -98,7 +104,7 @@ def test_parser(testcase: DataDrivenTestCase) -> None: try: with temp_source(source) as fnam: - node, errors, type_ignores = native_parse(fnam, options, skip_function_bodies) + node, errors, type_ignores = native_parse(fnam, options, None, skip_function_bodies) errors += load_tree(node, options) node.path = "main" a = node.str_with_options(options).split("\n") @@ -234,7 +240,7 @@ def format_reachable_imports(node: MypyFile) -> list[str]: @unittest.skipUnless(has_nativeparse, "nativeparse not available") class TestNativeParserBinaryFormat(unittest.TestCase): - def test_trivial_binary_data(self) -> None: + def _assert_trivial_binary_data(self, b: bytes, /) -> None: # A quick sanity check to ensure the serialized data looks as expected. Only covers # a few AST nodes. @@ -250,9 +256,9 @@ def locs(start_line: int, start_column: int, end_line: int, end_column: int) -> int_enc(end_column - start_column), ] - with temp_source("print('hello')") as fnam: - b, _, _, _, _, _, _, _ = parse_to_binary_ast(fnam, Options()) - assert list(b) == ( + self.assertEqual( + list(b), + ( [LITERAL_INT, 22, nodes.EXPR_STMT, nodes.CALL_EXPR] + [nodes.NAME_EXPR, LITERAL_STR] + [int_enc(5)] @@ -269,7 +275,25 @@ def locs(start_line: int, start_column: int, end_line: int, end_column: int) -> + [LIST_GEN, 22, LITERAL_NONE] + locs(1, 0, 1, 14) + [END_TAG, END_TAG] - ) + ), + ) + + def test_trivial_binary_data_from_file(self) -> None: + with temp_source("print('hello')") as fnam: + b, _, _, _, _, _, _, _ = parse_to_binary_ast(fnam, Options()) + self._assert_trivial_binary_data(b) + + def test_trivial_binary_data_from_string_source(self) -> None: + b, _, _, _, _, _, _, _ = parse_to_binary_ast("", Options(), "print('hello')") + self._assert_trivial_binary_data(b) + + def test_trivial_binary_data_from_bytes_source(self) -> None: + b, _, _, _, _, _, _, _ = parse_to_binary_ast("", Options(), b"print('hello')") + self._assert_trivial_binary_data(b) + + def test_invalid_bytes_raises(self) -> None: + with self.assertRaises(UnicodeDecodeError): + parse_to_binary_ast("", Options(), b"\xff") @contextlib.contextmanager diff --git a/mypy/test/testexportjson.py b/mypy/test/testexportjson.py index 58b0dea5e5a6d..294befcb3731c 100644 --- a/mypy/test/testexportjson.py +++ b/mypy/test/testexportjson.py @@ -11,6 +11,7 @@ from mypy.errors import CompileError from mypy.exportjson import convert_binary_cache_meta_to_json, convert_binary_cache_to_json from mypy.modulefinder import BuildSource +from mypy.modules_state import modules_state from mypy.options import Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite @@ -44,6 +45,11 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: major, minor = sys.version_info[:2] cache_dir = os.path.join(".mypy_cache", f"{major}.{minor}") + # Reset the global fixup state, since the exportjson tool + # reads cache files in isolation (no node fixer available). + modules_state.node_fixer = None + modules_state.modules = {} + for module in result.files: if module in ( "builtins", diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 6d00f5b5710f9..8f4de5bc7412b 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -66,7 +66,6 @@ def test_parser(testcase: DataDrivenTestCase) -> None: module="__main__", errors=errors, options=options, - file_exists=False, eager=True, ) if errors.is_errors(): @@ -108,7 +107,6 @@ def test_parse_error(testcase: DataDrivenTestCase) -> None: "__main__", errors=errors, options=options, - file_exists=False, eager=True, ) if errors.is_errors(): diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 71555c03b9ad4..858fd860fd01d 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -3135,7 +3135,6 @@ def test_get_typeshed_stdlib_modules(self) -> None: assert "os.path" in stdlib assert "asyncio" in stdlib assert "graphlib" not in stdlib - assert "formatter" in stdlib assert "contextvars" in stdlib # 3.7+ assert "importlib.metadata" not in stdlib diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index ec9af3e669344..6de922cb7f234 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -39,6 +39,7 @@ ProperType, TupleType, Type, + TypedDictType, TypeOfAny, TypeType, TypeVarId, @@ -223,6 +224,22 @@ def test_indirection_no_infinite_recursion(self) -> None: modules = visitor.modules assert modules == {"__main__", "builtins"} + def test_typeddict_type_constructor_signature(self) -> None: + typ = TypedDictType({"x": self.fx.o}, {"x"}, set(), self.fx.a, 10, 20) + + assert typ.fallback is self.fx.a + assert_equal(typ.line, 10) + assert_equal(typ.column, 20) + assert not typ.is_closed + + closed = TypedDictType({"x": self.fx.o}, {"x"}, set(), self.fx.a, is_closed=True) + assert closed.is_closed + + with self.assertRaises(TypeError): + TypedDictType( # type: ignore[call-arg] + {"x": self.fx.o}, {"x"}, set(), self.fx.a, 10, 20, True + ) + class TypeOpsSuite(Suite): def setUp(self) -> None: diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index e65f9f5ee3a42..1ff93fd891ef9 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -12,13 +12,8 @@ TypeVarTupleExpr, ) from mypy.types import ( - AnyType, ParamSpecFlavor, ParamSpecType, - TrivialSyntheticTypeTranslator, - Type, - TypeAliasType, - TypeOfAny, TypeVarId, TypeVarLikeType, TypeVarTupleType, @@ -28,54 +23,6 @@ FailFunc: _TypeAlias = Callable[[str, Context], None] -class TypeVarLikeDefaultFixer(TrivialSyntheticTypeTranslator): - """Set namespace for all TypeVarLikeTypes types.""" - - def __init__( - self, - scope: TypeVarLikeScope, - fail_func: FailFunc, - source_tv: TypeVarLikeExpr, - context: Context, - ) -> None: - self.scope = scope - self.fail_func = fail_func - self.source_tv = source_tv - self.context = context - super().__init__() - - def visit_type_var(self, t: TypeVarType) -> Type: - existing = self.scope.get_binding(t.fullname) - if existing is None: - self._report_unbound_tvar(t) - return AnyType(TypeOfAny.from_error) - return existing - - def visit_param_spec(self, t: ParamSpecType) -> Type: - existing = self.scope.get_binding(t.fullname) - if existing is None: - self._report_unbound_tvar(t) - return AnyType(TypeOfAny.from_error) - return existing - - def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type: - existing = self.scope.get_binding(t.fullname) - if existing is None: - self._report_unbound_tvar(t) - return AnyType(TypeOfAny.from_error) - return existing - - def visit_type_alias_type(self, t: TypeAliasType) -> Type: - return t - - def _report_unbound_tvar(self, tvar: TypeVarLikeType) -> None: - self.fail_func( - f"Type variable {tvar.name} referenced in the default" - f" of {self.source_tv.name} is unbound", - self.context, - ) - - class TypeVarLikeScope: """Scope that holds bindings for type variables and parameter specifications. @@ -148,15 +95,6 @@ def bind_new( i = self.func_id namespace = self.namespace - # Defaults may reference other type variables. That is only valid when the - # referenced variable is already in scope (textually precedes the definition we're - # processing now). - default = tvar_expr.default.accept( - TypeVarLikeDefaultFixer( - self, fail_func=fail_func, source_tv=tvar_expr, context=context - ) - ) - if isinstance(tvar_expr, TypeVarExpr): tvar_def: TypeVarLikeType = TypeVarType( name=name, @@ -164,7 +102,7 @@ def bind_new( id=TypeVarId(i, namespace=namespace), values=tvar_expr.values, upper_bound=tvar_expr.upper_bound, - default=default, + default=tvar_expr.default, variance=tvar_expr.variance, line=tvar_expr.line, column=tvar_expr.column, @@ -176,7 +114,7 @@ def bind_new( id=TypeVarId(i, namespace=namespace), flavor=ParamSpecFlavor.BARE, upper_bound=tvar_expr.upper_bound, - default=default, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) @@ -187,7 +125,7 @@ def bind_new( id=TypeVarId(i, namespace=namespace), upper_bound=tvar_expr.upper_bound, tuple_fallback=tvar_expr.tuple_fallback, - default=default, + default=tvar_expr.default, line=tvar_expr.line, column=tvar_expr.column, ) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 1b38481ba0004..7052c80118710 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -34,6 +34,7 @@ ParamSpecType, PartialType, PlaceholderType, + ProperType, RawExpressionType, TupleType, Type, @@ -254,10 +255,15 @@ def visit_unpack_type(self, t: UnpackType, /) -> Type: return UnpackType(t.type.accept(self)) def visit_callable_type(self, t: CallableType, /) -> Type: + instance_type = None + if t.instance_type is not None: + instance_type = t.instance_type.accept(self) + assert isinstance(instance_type, ProperType) return t.copy_modified( arg_types=self.translate_type_list(t.arg_types), ret_type=t.ret_type.accept(self), variables=self.translate_variables(t.variables), + instance_type=instance_type, ) def visit_tuple_type(self, t: TupleType, /) -> Type: @@ -282,6 +288,7 @@ def visit_typeddict_type(self, t: TypedDictType, /) -> Type: cast(Any, t.fallback.accept(self)), t.line, t.column, + is_closed=t.is_closed, ) self.set_cached(t, result) return result @@ -415,7 +422,11 @@ def visit_instance(self, t: Instance, /) -> T: def visit_callable_type(self, t: CallableType, /) -> T: # FIX generics - return self.query_types(t.arg_types + [t.ret_type]) + types = t.arg_types + [t.ret_type] + # Avoid double-counting when using queries in reports. + if t.instance_type is not None and t.instance_type != t.ret_type: + types.append(t.instance_type) + return self.query_types(types) def visit_tuple_type(self, t: TupleType, /) -> T: return self.query_types([t.partial_fallback] + t.items) @@ -551,12 +562,11 @@ def visit_instance(self, t: Instance, /) -> bool: def visit_callable_type(self, t: CallableType, /) -> bool: # FIX generics # Avoid allocating any objects here as an optimization. - args = self.query_types(t.arg_types) - ret = t.ret_type.accept(self) + inst = t.instance_type.accept(self) if t.instance_type is not None else False if self.strategy == ANY_STRATEGY: - return args or ret + return self.query_types(t.arg_types) or t.ret_type.accept(self) or inst else: - return args and ret + return self.query_types(t.arg_types) and t.ret_type.accept(self) and inst def visit_tuple_type(self, t: TupleType, /) -> bool: return self.query_types([t.partial_fallback] + t.items) @@ -595,7 +605,15 @@ def visit_type_alias_type(self, t: TypeAliasType, /) -> bool: elif t in self.seen_aliases: return self.default self.seen_aliases.add(t) - return get_proper_type(t).accept(self) + res = get_proper_type(t).accept(self) + # This is a weird edge case: if a type alias has unused type variables, we + # should visit arguments even if we didn't find anything in the expansion. + # As an optimization, do this only for new style type aliases. + assert t.alias is not None + if self.strategy == ANY_STRATEGY: + return res or (t.alias.python_3_12_type_alias and self.query_types(t.args)) + else: + return res and (not t.alias.python_3_12_type_alias or self.query_types(t.args)) def query_types(self, types: list[Type] | tuple[Type, ...]) -> bool: """Perform a query for a sequence of types using the strategy to combine the results.""" diff --git a/mypy/typeanal.py b/mypy/typeanal.py index db56256192625..ff3c8bd2816e1 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -75,6 +75,7 @@ BoolTypeQuery, CallableArgument, CallableType, + CollectAliasesVisitor, DeletedType, EllipsisType, ErasedType, @@ -154,6 +155,7 @@ def analyze_type_alias( in_dynamic_func: bool = False, global_scope: bool = True, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + erase_tvar_defs: list[TypeVarType] | None = None, alias_type_params_names: list[str] | None = None, python_3_12_type_alias: bool = False, ) -> tuple[Type, set[str]]: @@ -174,6 +176,7 @@ def analyze_type_alias( allow_placeholder=allow_placeholder, prohibit_self_type="type alias target", allowed_alias_tvars=allowed_alias_tvars, + erase_tvar_defs=erase_tvar_defs, alias_type_params_names=alias_type_params_names, python_3_12_type_alias=python_3_12_type_alias, ) @@ -220,8 +223,10 @@ def __init__( prohibit_self_type: str | None = None, prohibit_special_class_field_types: str | None = None, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + erase_tvar_defs: list[TypeVarType] | None = None, allow_type_any: bool = False, alias_type_params_names: list[str] | None = None, + analyzing_tvar_def: bool = False, ) -> None: self.api = api self.fail_func = api.fail @@ -239,6 +244,11 @@ def __init__( if allowed_alias_tvars is None: allowed_alias_tvars = [] self.allowed_alias_tvars = allowed_alias_tvars + # Should we erase some type variables? This can be used to mass-erase type + # variables that were found to be invalid at the class/alias definition. + if erase_tvar_defs is None: + erase_tvar_defs = [] + self.erase_tvar_defs = erase_tvar_defs self.alias_type_params_names = alias_type_params_names # If false, record incomplete ref if we generate PlaceholderType. self.allow_placeholder = allow_placeholder @@ -266,8 +276,12 @@ def __init__( self.prohibit_special_class_field_types = prohibit_special_class_field_types # Allow variables typed as Type[Any] and type (useful for base classes). self.allow_type_any = allow_type_any - self.allow_type_var_tuple = False + # Level of nesting at which a TypeVarTuple is allowed. Note we specify exact level + # to prohibit things like Unpack[list[Ts]], which are not supported. + self.allow_type_var_tuple = -1 self.allow_unpack = allow_unpack + # Set when we are analyzing a default of a type variable. + self.analyzing_tvar_def = analyzing_tvar_def def lookup_qualified( self, name: str, ctx: Context, suppress_errors: bool = False @@ -400,6 +414,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) msg = f'Can\'t use bound type variable "{t.name}" to define generic alias' self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) + if ( + isinstance(sym.node, TypeVarExpr) + and tvar_def is not None + and tvar_def in self.erase_tvar_defs + ): + # The caller should have already given a relevant error. + return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None: assert isinstance(tvar_def, TypeVarType) if len(t.args) > 0: @@ -435,7 +456,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, TypeVarTupleType) - if not self.allow_type_var_tuple: + if self.allow_type_var_tuple != self.nesting_level: self.fail( f'TypeVarTuple "{t.name}" is only valid with an unpack', t, @@ -473,17 +494,22 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) an_args = self.pack_paramspec_args(an_args, t.empty_tuple_index) disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub - res = instantiate_type_alias( + res, used_default = instantiate_type_alias( node, an_args, self.fail, + self.note, node.no_args, t, self.options, unexpanded_type=t, disallow_any=disallow_any, empty_tuple_index=t.empty_tuple_index, + analyzing_tvar_def=self.analyzing_tvar_def, ) + if self.analyzing_tvar_def and used_default and isinstance(res, TypeAliasType): + assert res.alias is not None + self.api.record_fixed_type(res.alias) # The only case where instantiate_type_alias() can return an incorrect instance is # when it is top-level instance, so no need to recurse. if ( @@ -492,7 +518,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) and not (self.defining_alias and self.nesting_level == 0) and not validate_instance(res, self.fail, t.empty_tuple_index) ): - fix_instance( + used_default = fix_instance( res, self.fail, self.note, @@ -500,7 +526,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) options=self.options, use_generic_error=True, unexpanded_type=t, + analyzing_tvar_def=self.analyzing_tvar_def, ) + if self.analyzing_tvar_def and used_default: + self.api.record_fixed_type(res.type) if node.eager: res = get_proper_type(res) return res @@ -782,9 +811,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ if not self.allow_unpack: self.fail(message_registry.INVALID_UNPACK_POSITION, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) - self.allow_type_var_tuple = True + self.allow_type_var_tuple = self.nesting_level + 1 result = UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) - self.allow_type_var_tuple = False + self.allow_type_var_tuple = -1 return result elif fullname in SELF_TYPE_NAMES: if t.args: @@ -873,33 +902,47 @@ def analyze_type_with_type_info( return AnyType(TypeOfAny.from_error) # Check type argument count. + old_args = instance.args instance.args = tuple(flatten_nested_tuples(instance.args)) + if old_args and not instance.args: + empty_tuple_index = True if not (self.defining_alias and self.nesting_level == 0) and not validate_instance( instance, self.fail, empty_tuple_index ): - fix_instance( + used_default = fix_instance( instance, self.fail, self.note, disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub, options=self.options, + analyzing_tvar_def=self.analyzing_tvar_def, ) + if self.analyzing_tvar_def and used_default: + self.api.record_fixed_type(info) tup = info.tuple_type if tup is not None: # The class has a Tuple[...] base class so it will be # represented as a tuple type. if info.special_alias: - return instantiate_type_alias( + res, used_default = instantiate_type_alias( info.special_alias, # TODO: should we allow NamedTuples generic in ParamSpec? self.anal_array(args, allow_unpack=True), self.fail, + self.note, False, ctx, self.options, use_standard_error=True, + empty_tuple_index=empty_tuple_index, + analyzing_tvar_def=self.analyzing_tvar_def, ) + if self.analyzing_tvar_def and used_default: + # For convenience, we make default depend on the original TypeInfo, + # *not* on the special alias. + self.api.record_fixed_type(info) + return res return tup.copy_modified( items=self.anal_array(tup.items, allow_unpack=True), fallback=instance ) @@ -908,16 +951,23 @@ def analyze_type_with_type_info( # The class has a TypedDict[...] base class so it will be # represented as a typeddict type. if info.special_alias: - return instantiate_type_alias( + res, used_default = instantiate_type_alias( info.special_alias, # TODO: should we allow TypedDicts generic in ParamSpec? self.anal_array(args, allow_unpack=True), self.fail, + self.note, False, ctx, self.options, use_standard_error=True, + analyzing_tvar_def=self.analyzing_tvar_def, ) + if self.analyzing_tvar_def and used_default: + # For convenience, we make default depend on the original TypeInfo, + # *not* on the special alias. + self.api.record_fixed_type(info) + return res # Create a named TypedDictType return td.copy_modified( item_types=self.anal_array(list(td.items.values())), fallback=instance @@ -1114,9 +1164,9 @@ def visit_unpack_type(self, t: UnpackType) -> Type: if not self.allow_unpack: self.fail(message_registry.INVALID_UNPACK_POSITION, t.type, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) - self.allow_type_var_tuple = True + self.allow_type_var_tuple = self.nesting_level + 1 result = UnpackType(self.anal_type(t.type), from_star_syntax=t.from_star_syntax) - self.allow_type_var_tuple = False + self.allow_type_var_tuple = -1 return result def visit_parameters(self, t: Parameters) -> Type: @@ -1341,6 +1391,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type: " must be enabled with --enable-incomplete-feature=InlineTypedDict", t, ) + is_closed = False required_keys = req_keys fallback = self.named_type("typing._TypedDict") for typ in t.extra_items_from: @@ -1360,9 +1411,13 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type: if sub_item_name in p_analyzed.readonly_keys: readonly_keys.add(sub_item_name) else: + readonly_keys = t.readonly_keys required_keys = t.required_keys fallback = t.fallback - return TypedDictType(items, required_keys, readonly_keys, fallback, t.line, t.column) + is_closed = t.is_closed + return TypedDictType( + items, required_keys, readonly_keys, fallback, t.line, t.column, is_closed=is_closed + ) def visit_raw_expression_type(self, t: RawExpressionType) -> Type: # We should never see a bare Literal. We synthesize these raw literals @@ -2026,6 +2081,7 @@ def get_omitted_any( options: Options, fullname: str | None = None, unexpanded_type: Type | None = None, + used_default: bool = False, ) -> AnyType: if disallow_any: typ = unexpanded_type or orig_type @@ -2036,6 +2092,8 @@ def get_omitted_any( typ, code=codes.TYPE_ARG, ) + if used_default: + note(message_registry.NO_CYCLIC_DEFAULT, typ, code=codes.TYPE_ARG) any_type = AnyType(TypeOfAny.from_error, line=typ.line, column=typ.column) else: @@ -2065,13 +2123,18 @@ def fix_instance( options: Options, use_generic_error: bool = False, unexpanded_type: Type | None = None, -) -> None: + analyzing_tvar_def: bool = False, +) -> bool: """Fix a malformed instance by replacing all type arguments with TypeVar default or Any. Also emit a suitable error if this is not due to implicit Any's. """ + used_default = False arg_count = len(t.args) - min_tv_count = sum(not tv.has_default() for tv in t.type.defn.type_vars) + min_tv_count = sum( + not tv.has_default() and not isinstance(tv, TypeVarTupleType) + for tv in t.type.defn.type_vars + ) max_tv_count = len(t.type.type_vars) if arg_count < min_tv_count or arg_count > max_tv_count: # Don't use existing args if arg_count doesn't match @@ -2080,38 +2143,71 @@ def fix_instance( disallow_any = False t.args = () - args: list[Type] = [*(t.args[:max_tv_count])] + args: list[Type] = list(t.args) any_type: AnyType | None = None env: dict[TypeVarId, Type] = {} + tvt_no_default = False for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None): if tv is None: continue if arg is None: + use_any = False if tv.has_default(): arg = tv.default + if analyzing_tvar_def: + # Record the use of default only when analyzing another default. + used_default = True + if is_typevar_default_recursive(tv.fullname, t.type): + # If this results in infinite recursion, use Any instead. + use_any = True else: + use_any = True + if use_any: if any_type is None: fullname = None if use_generic_error else t.type.fullname any_type = get_omitted_any( - disallow_any, fail, note, t, options, fullname, unexpanded_type + disallow_any, + fail, + note, + t, + options, + fullname, + unexpanded_type, + used_default, ) arg = any_type - args.append(arg) - env[tv.id] = arg + else: + assert arg is not None + if use_any and isinstance(tv, TypeVarTupleType): + tvt_no_default = True + # Default such as *tuple[int, str] should be unpacked into individual items. + if isinstance(arg, UnpackType) and isinstance( + unpack := get_proper_type(arg.type), TupleType + ): + unpacked = unpack.items + else: + unpacked = [arg] + for arg in unpacked: + with state.strict_optional_set(options.strict_optional): + # Gradually expand defaults, as they may depend on previous variables. + if tv.has_default(): + arg = expand_type(arg, env) + env[tv.id] = arg + args.append(arg) + else: + env[tv.id] = arg t.args = tuple(args) - fix_type_var_tuple_argument(t) - if not t.type.has_type_var_tuple_type: - with state.strict_optional_set(True): - fixed = expand_type(t, env) - assert isinstance(fixed, Instance) - t.args = fixed.args + if tvt_no_default: + fix_type_var_tuple_argument(t) + return used_default def instantiate_type_alias( node: TypeAlias, args: list[Type], fail: MsgCallback, + note: MsgCallback, no_args: bool, ctx: Context, options: Options, @@ -2120,7 +2216,8 @@ def instantiate_type_alias( disallow_any: bool = False, use_standard_error: bool = False, empty_tuple_index: bool = False, -) -> Type: + analyzing_tvar_def: bool = False, +) -> tuple[Type, bool]: """Create an instance of a (generic) type alias from alias node and type arguments. We are following the rules outlined in TypeAlias docstring. @@ -2136,7 +2233,10 @@ def instantiate_type_alias( # Type aliases are special, since they can be expanded during semantic analysis, # so we need to normalize them as soon as possible. # TODO: can this cause an infinite recursion? + old_args = args args = flatten_nested_tuples(args) + if old_args and not args: + empty_tuple_index = True if any(unknown_unpack(a) for a in args): # This type is not ready to be validated, because of unknown total count. # Note that we keep the kind of Any for consistency. @@ -2167,15 +2267,17 @@ def instantiate_type_alias( options, disallow_any=disallow_any, fail=fail, + note=note, unexpanded_type=unexpanded_type, + analyzing_tvar_def=analyzing_tvar_def, ) if max_tv_count == 0 and act_len == 0: if no_args: assert isinstance(node.target, Instance) # type: ignore[misc] # Note: this is the only case where we use an eager expansion. See more info about # no_args aliases like L = List in the docstring for TypeAlias class. - return Instance(node.target.type, [], line=ctx.line, column=ctx.column) - return TypeAliasType(node, [], line=ctx.line, column=ctx.column) + return Instance(node.target.type, [], line=ctx.line, column=ctx.column), False + return TypeAliasType(node, [], line=ctx.line, column=ctx.column), False if ( max_tv_count == 0 and act_len > 0 @@ -2187,7 +2289,7 @@ def instantiate_type_alias( tp.column = ctx.column tp.end_line = ctx.end_line tp.end_column = ctx.end_column - return tp + return tp, False if node.tvar_tuple_index is None: if any(isinstance(a, UnpackType) for a in args): # A variadic unpack in fixed size alias (fixed unpacks must be flattened by the caller) @@ -2233,7 +2335,18 @@ def instantiate_type_alias( ) fail(msg, ctx, code=codes.TYPE_ARG) args = [] - return set_any_tvars(node, args, ctx.line, ctx.column, options, from_error=True) + return set_any_tvars( + node, + args, + ctx.line, + ctx.column, + options, + disallow_any=disallow_any, + fail=fail, + note=note, + from_error=not correct, + analyzing_tvar_def=analyzing_tvar_def, + ) elif node.tvar_tuple_index is not None: # We also need to check if we are not performing a type variable tuple split. unpack = find_unpack_in_list(args) @@ -2260,8 +2373,8 @@ def instantiate_type_alias( ): exp = get_proper_type(typ) assert isinstance(exp, Instance) - return exp.args[-1] - return typ + return exp.args[-1], False + return typ, False def set_any_tvars( @@ -2275,8 +2388,11 @@ def set_any_tvars( disallow_any: bool = False, special_form: bool = False, fail: MsgCallback | None = None, + note: MsgCallback | None = None, unexpanded_type: Type | None = None, -) -> TypeAliasType: + analyzing_tvar_def: bool = False, +) -> tuple[TypeAliasType, bool]: + used_default = False if from_error or disallow_any: type_of_any = TypeOfAny.from_error elif special_form: @@ -2287,30 +2403,42 @@ def set_any_tvars( env: dict[TypeVarId, Type] = {} used_any_type = False - has_type_var_tuple_type = False for tv, arg in itertools.zip_longest(node.alias_tvars, args, fillvalue=None): if tv is None: continue if arg is None: if tv.has_default(): arg = tv.default + # Same as for instances, record and avoid infinite recursion. + if analyzing_tvar_def: + used_default = True + if is_typevar_default_recursive(tv.fullname, node): + arg = any_type + used_any_type = True else: arg = any_type used_any_type = True - if isinstance(tv, TypeVarTupleType): - # TODO Handle TypeVarTuple defaults - has_type_var_tuple_type = True + if used_any_type and isinstance(tv, TypeVarTupleType): arg = UnpackType(Instance(tv.tuple_fallback.type, [any_type])) - args.append(arg) - env[tv.id] = arg + # Default such as *tuple[int, str] should be unpacked into individual items. + if isinstance(arg, UnpackType) and isinstance( + unpack := get_proper_type(arg.type), TupleType + ): + unpacked = unpack.items + else: + unpacked = [arg] + for arg in unpacked: + with state.strict_optional_set(options.strict_optional): + # Gradually expand defaults, as they may depend on previous variables. + if tv.has_default(): + arg = expand_type(arg, env) + env[tv.id] = arg + args.append(arg) + else: + env[tv.id] = arg t = TypeAliasType(node, args, newline, newcolumn) - if not has_type_var_tuple_type: - with state.strict_optional_set(options.strict_optional): - fixed = expand_type(t, env) - assert isinstance(fixed, TypeAliasType) - t.args = fixed.args - if used_any_type and disallow_any and node.alias_tvars: + if used_any_type and disallow_any and node.alias_tvars and not from_error: assert fail is not None if unexpanded_type: type_str = ( @@ -2326,7 +2454,34 @@ def set_any_tvars( Context(newline, newcolumn), code=codes.TYPE_ARG, ) - return t + if used_default: + assert note is not None + note( + message_registry.NO_CYCLIC_DEFAULT, + Context(newline, newcolumn), + code=codes.TYPE_ARG, + ) + return t, used_default + + +def is_typevar_default_recursive(tv_fname: str, start: TypeInfo | TypeAlias) -> bool: + """Check if the type variable can lead to infinite recursion via defaults.""" + if tv_fname not in start.default_depends: + return False + todo = start.default_depends[tv_fname].copy() + seen: set[TypeAlias | TypeInfo] = set() + while todo: + node = todo.pop() + if node is start: + return True + if node in seen: + # We don't return True here, since we are interested only in + # recursion via the original type variable. + continue + seen.add(node) + for dep_nodes in node.default_depends.values(): + todo |= dep_nodes + return False class DivergingAliasDetector(TrivialSyntheticTypeTranslator): @@ -2371,6 +2526,15 @@ def detect_diverging_alias(node: TypeAlias, target: Type) -> bool: They may be handy in rare cases, e.g. to express a union of non-mixed nested lists: Nested = Union[T, Nested[List[T]]] ~> Union[T, List[T], List[List[T]], ...] """ + is_recursive = node._is_recursive + if is_recursive is None: + is_recursive = node in node.target.accept(CollectAliasesVisitor()) + if not is_recursive: + # Fast path: this is not a recursive alias at all. + return False + # Note we only cache positive case, caching negative case is risky, as this type alias + # (or more importantly any other alias it uses) may be not ready yet. + node._is_recursive = True visitor = DivergingAliasDetector({node}) _ = target.accept(visitor) return visitor.diverging diff --git a/mypy/typeops.py b/mypy/typeops.py index e13d6dd0b1732..78448c60927f9 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -217,7 +217,9 @@ def type_object_type( is_bound=True, fallback=instance_cache.function_type, ) - result: FunctionLike = class_callable(sig, info, fallback, None, is_new=False) + result: FunctionLike = class_callable( + sig, info, None, fallback, None, is_new=False + ) if allow_cache and state.strict_optional: info.type_object_type = result return result @@ -240,6 +242,12 @@ def type_object_type( assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this t = method.type result = type_object_type_from_function(t, info, method.info, fallback, is_new) + # Tuple constructor in typeshed is imprecise (and precise one is impossible to express), + # so we special-case constructors for tuple types. Note we skip the tuple class itself + # as a micro-optimization, since it is unlikely one would write tuple((1, 2)). + if method.info.fullname == "builtins.tuple" and info.fullname != "builtins.tuple": + assert isinstance(result, CallableType) + result = result.copy_modified(special_sig="tuple") # Only write cached result is strict_optional=True, otherwise we may get # inconsistent behaviour because of union simplification. if allow_cache and state.strict_optional: @@ -299,19 +307,24 @@ def type_object_type_from_function( special_sig = "dict" if isinstance(signature, CallableType): - return class_callable(signature, info, fallback, special_sig, is_new, orig_self_types[0]) + return class_callable( + signature, info, def_info, fallback, special_sig, is_new, orig_self_types[0] + ) else: # Overloaded __init__/__new__. assert isinstance(signature, Overloaded) items: list[CallableType] = [] for item, orig_self in zip(signature.items, orig_self_types): - items.append(class_callable(item, info, fallback, special_sig, is_new, orig_self)) + items.append( + class_callable(item, info, def_info, fallback, special_sig, is_new, orig_self) + ) return Overloaded(items) def class_callable( init_type: CallableType, info: TypeInfo, + def_info: TypeInfo | None, type_type: Instance, special_sig: str | None, is_new: bool, @@ -322,35 +335,51 @@ def class_callable( variables.extend(info.defn.type_vars) variables.extend(init_type.variables) - from mypy.subtypes import is_subtype + from mypy.subtypes import is_equivalent, is_subtype init_ret_type = get_proper_type(init_type.ret_type) orig_self_type = get_proper_type(orig_self_type) default_ret_type = fill_typevars(info) + # Default return type in the class where constructor method was defined. + default_def_ret_type = fill_typevars(def_info) if def_info is not None else default_ret_type explicit_type = init_ret_type if is_new else orig_self_type if ( + is_new + and explicit_type is not None + # We used to only use the explicit return type of __new__() when it was a subtype + # of the current class. As a result, we may now have a situation like this: + # class C: + # def __new__(cls) -> C: ... + # class D(C): ... + # So we need to ignore the explicit annotation when creating constructor type for D. + and ( + isinstance(explicit_type, AnyType) + and explicit_type.type_of_any != TypeOfAny.unannotated + or not is_equivalent(default_def_ret_type, explicit_type, ignore_type_params=True) + ) + ): + ret_type = explicit_type + elif ( isinstance(explicit_type, (Instance, TupleType, UninhabitedType, LiteralType)) # We have to skip protocols, because it can be a subtype of a return type # by accident. Like `Hashable` is a subtype of `object`. See #11799 and isinstance(default_ret_type, Instance) and not default_ret_type.type.is_protocol - # Only use the declared return type from __new__ or declared self in __init__ - # if it is actually returning a subtype of what we would return otherwise. + # Use the declared self in __init__ if it is a subtype of what we would use otherwise. and is_subtype(explicit_type, default_ret_type, ignore_type_params=True) ): - ret_type: Type = explicit_type + ret_type = explicit_type else: ret_type = default_ret_type - callable_type = init_type.copy_modified( + return init_type.copy_modified( ret_type=ret_type, fallback=type_type, - name=None, + name=info.name, variables=variables, special_sig=special_sig, + instance_type=default_ret_type, ) - c = callable_type.with_name(info.name) - return c def map_type_from_supertype(typ: Type, sub_info: TypeInfo, super_info: TypeInfo) -> Type: @@ -474,7 +503,7 @@ class B(A): pass # Solve for these type arguments using the actual class or instance type. typeargs = infer_type_arguments( - self_vars, self_param_type, original_type, is_supertype=True + self_vars, self_param_type, original_type, is_supertype=True, erase_types=False ) if ( is_classmethod @@ -483,7 +512,11 @@ class B(A): pass ): # In case we call a classmethod through an instance x, fallback to type(x). typeargs = infer_type_arguments( - self_vars, self_param_type, TypeType(original_type), is_supertype=True + self_vars, + self_param_type, + TypeType(original_type), + is_supertype=True, + erase_types=False, ) # Update the method signature with the solutions found. diff --git a/mypy/types.py b/mypy/types.py index 40c3839e2efca..da420d1c012d2 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -9,6 +9,7 @@ Any, ClassVar, Final, + NamedTuple, NewType, TypeAlias as _TypeAlias, TypeGuard, @@ -37,6 +38,7 @@ Tag, WriteBuffer, read_bool, + read_flags, read_int, read_int_list, read_literal, @@ -46,6 +48,7 @@ read_str_opt_list, read_tag, write_bool, + write_flags, write_int, write_int_list, write_literal, @@ -699,6 +702,7 @@ def __eq__(self, other: object) -> bool: self.id == other.id and self.upper_bound == other.upper_bound and self.values == other.values + and self.default == other.default ) def serialize(self) -> JsonDict: @@ -854,7 +858,12 @@ def __eq__(self, other: object) -> bool: if not isinstance(other, ParamSpecType): return NotImplemented # Upper bound can be ignored, since it's determined by flavor. - return self.id == other.id and self.flavor == other.flavor and self.prefix == other.prefix + return ( + self.id == other.id + and self.flavor == other.flavor + and self.prefix == other.prefix + and self.default == other.default + ) def serialize(self) -> JsonDict: assert not self.id.is_meta_var() @@ -1003,7 +1012,9 @@ def __hash__(self) -> int: def __eq__(self, other: object) -> bool: if not isinstance(other, TypeVarTupleType): return NotImplemented - return self.id == other.id and self.min_len == other.min_len + return ( + self.id == other.id and self.min_len == other.min_len and self.default == other.default + ) def copy_modified( self, @@ -1389,9 +1400,9 @@ class UninhabitedType(ProperType): ambiguous: bool # Is this a result of inference for a variable without constraints? - def __init__(self, line: int = -1, column: int = -1) -> None: + def __init__(self, line: int = -1, column: int = -1, *, ambiguous: bool = False) -> None: super().__init__(line, column) - self.ambiguous = False + self.ambiguous = ambiguous def can_be_true_default(self) -> bool: return False @@ -2139,7 +2150,6 @@ class CallableType(FunctionLike): "arg_types", # Types of function arguments "arg_kinds", # ARG_ constants "arg_names", # Argument names; None if not a keyword argument - "min_args", # Minimum number of arguments; derived from arg_kinds "ret_type", # Return value type "name", # Name (may be None; for error messages and plugins) "definition", # For error messages. May be None. @@ -2149,7 +2159,7 @@ class CallableType(FunctionLike): # specified by the user? "special_sig", # Non-None for signatures that require special handling # (currently only values are 'dict' for a signature similar to - # 'dict' and 'partial' for a `functools.partial` evaluation) + # 'dict' and 'partial' for a `functools.partial` evaluation, and 'tuple') "from_type_type", # Was this callable generated by analyzing Type[...] # instantiation? "is_bound", # Is this a bound method? @@ -2159,6 +2169,8 @@ class CallableType(FunctionLike): # (this is used for error messages) "imprecise_arg_kinds", "unpack_kwargs", # Was an Unpack[...] with **kwargs used to define this callable? + "instance_type", # Real underlying type of a type object. This is different from + # ret_type in case we have e.g. a custom __new__() return annotation. ) def __init__( @@ -2184,6 +2196,7 @@ def __init__( from_concatenate: bool = False, imprecise_arg_kinds: bool = False, unpack_kwargs: bool = False, + instance_type: ProperType | None = None, ) -> None: super().__init__(line, column) assert len(arg_types) == len(arg_kinds) == len(arg_names) @@ -2195,7 +2208,6 @@ def __init__( # See testParamSpecJoin, that relies on passing e.g `P.args` as plain argument. self.arg_kinds = arg_kinds self.arg_names = list(arg_names) - self.min_args = arg_kinds.count(ARG_POS) self.ret_type = ret_type self.fallback = fallback assert not name or " CT: modified = CallableType( arg_types=arg_types if arg_types is not _dummy else self.arg_types, @@ -2272,6 +2286,7 @@ def copy_modified( else self.imprecise_arg_kinds ), unpack_kwargs=unpack_kwargs if unpack_kwargs is not _dummy else self.unpack_kwargs, + instance_type=instance_type if instance_type is not _dummy else self.instance_type, ) # Optimization: Only NewTypes are supported as subtypes since # the class is effectively final, so we can use a cast safely. @@ -2291,6 +2306,10 @@ def kw_arg(self) -> FormalArgument | None: return FormalArgument(None, position, type, False) return None + @property + def min_args(self) -> int: + return self.arg_kinds.count(ARG_POS) + @property def is_var_arg(self) -> bool: """Does this callable have a *args argument?""" @@ -2306,9 +2325,23 @@ def is_type_obj(self) -> bool: get_proper_type(self.ret_type), UninhabitedType ) - def type_object(self) -> mypy.nodes.TypeInfo: + def get_instance_type(self, *, force_fallback: bool = False) -> ProperType: + """Get underlying type of a type object. + + By default, this will return a precise self-type, essentially whatever is + returned by fill_typevars(). Most notably this is a TupleType for named tuples. + If an Instance fallback is required, use force_fallback=True. + """ assert self.is_type_obj() - ret = get_proper_type(self.ret_type) + if self.instance_type is not None: + ret = self.instance_type + else: + # Fall back to historic behavior in case instance_type is not set. This + # will avoid crashes on type objects generated by plugins, and on (unknown) + # corner cases where is_type_obj() may "accidentally" return True. + ret = get_proper_type(self.ret_type) + if not force_fallback: + return ret if isinstance(ret, TypeVarType): ret = get_proper_type(ret.upper_bound) if isinstance(ret, TupleType): @@ -2317,15 +2350,19 @@ def type_object(self) -> mypy.nodes.TypeInfo: ret = ret.fallback if isinstance(ret, LiteralType): ret = ret.fallback - assert isinstance(ret, Instance) - return ret.type + return ret + + def type_object(self) -> mypy.nodes.TypeInfo: + instance_type = self.get_instance_type(force_fallback=True) + assert isinstance(instance_type, Instance) + return instance_type.type def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_callable_type(self) def with_name(self, name: str) -> CallableType: """Return a copy of this type with the specified name.""" - return self.copy_modified(ret_type=self.ret_type, name=name) + return self.copy_modified(name=name) def get_name(self) -> str | None: return self.name @@ -2583,10 +2620,13 @@ def serialize(self) -> JsonDict: "implicit": self.implicit, "is_bound": self.is_bound, "type_guard": self.type_guard.serialize() if self.type_guard is not None else None, - "type_is": (self.type_is.serialize() if self.type_is is not None else None), + "type_is": self.type_is.serialize() if self.type_is is not None else None, "from_concatenate": self.from_concatenate, "imprecise_arg_kinds": self.imprecise_arg_kinds, "unpack_kwargs": self.unpack_kwargs, + "instance_type": ( + self.instance_type.serialize() if self.instance_type is not None else None + ), } @classmethod @@ -2607,35 +2647,56 @@ def deserialize(cls, data: JsonDict) -> CallableType: type_guard=( deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None ), - type_is=(deserialize_type(data["type_is"]) if data["type_is"] is not None else None), + type_is=deserialize_type(data["type_is"]) if data["type_is"] is not None else None, from_concatenate=data["from_concatenate"], imprecise_arg_kinds=data["imprecise_arg_kinds"], unpack_kwargs=data["unpack_kwargs"], + instance_type=( + cast(ProperType, deserialize_type(data["instance_type"])) + if data["instance_type"] is not None + else None + ), ) def write(self, data: WriteBuffer) -> None: write_tag(data, CALLABLE_TYPE) self.fallback.write(data) + write_type_opt(data, self.instance_type) + write_flags( + data, + [ + self.is_ellipsis_args, + self.implicit, + self.is_bound, + self.from_concatenate, + self.imprecise_arg_kinds, + self.unpack_kwargs, + ], + ) write_type_list(data, self.arg_types) write_int_list(data, [int(x.value) for x in self.arg_kinds]) write_str_opt_list(data, self.arg_names) self.ret_type.write(data) write_str_opt(data, self.name) write_type_list(data, self.variables) - write_bool(data, self.is_ellipsis_args) - write_bool(data, self.implicit) - write_bool(data, self.is_bound) write_type_opt(data, self.type_guard) write_type_opt(data, self.type_is) - write_bool(data, self.from_concatenate) - write_bool(data, self.imprecise_arg_kinds) - write_bool(data, self.unpack_kwargs) write_tag(data, END_TAG) @classmethod def read(cls, data: ReadBuffer) -> CallableType: assert read_tag(data) == INSTANCE fallback = Instance.read(data) + instance_type = read_type_opt(data) + assert instance_type is None or isinstance(instance_type, ProperType) + ( + is_ellipsis_args, + implicit, + is_bound, + from_concatenate, + imprecise_arg_kinds, + unpack_kwargs, + ) = read_flags(data, num_flags=6) ret = CallableType( read_type_list(data), [ARG_KINDS[ak] for ak in read_int_list(data)], @@ -2644,14 +2705,15 @@ def read(cls, data: ReadBuffer) -> CallableType: fallback, name=read_str_opt(data), variables=read_type_var_likes(data), - is_ellipsis_args=read_bool(data), - implicit=read_bool(data), - is_bound=read_bool(data), + is_ellipsis_args=is_ellipsis_args, + implicit=implicit, + is_bound=is_bound, type_guard=read_type_opt(data), type_is=read_type_opt(data), - from_concatenate=read_bool(data), - imprecise_arg_kinds=read_bool(data), - unpack_kwargs=read_bool(data), + from_concatenate=from_concatenate, + imprecise_arg_kinds=imprecise_arg_kinds, + unpack_kwargs=unpack_kwargs, + instance_type=instance_type, ) assert read_tag(data) == END_TAG return ret @@ -2923,6 +2985,26 @@ def slice( return TupleType(slice_items, fallback, self.line, self.column, self.implicit) +class TypedDictItem(NamedTuple): + """Type, mutability and requiredness of an item in a TypedDict. + + If typ is `None`, the item comes from a missing item in an open TypedDict, and + the type should be treated as if it were a `builtins.object`. (Missing items in + closed TypedDicts will have an uninhabited type.) + + TODO: pass a `builtins.object` instead of None when TypedDictType gains a + proper extra_items field. + """ + + typ: Type | None + required: bool + readonly: bool + + @property + def mutable(self) -> bool: + return not self.readonly + + class TypedDictType(ProperType): """Type of TypedDict object {'k1': v1, ..., 'kn': vn}. @@ -2947,6 +3029,7 @@ class TypedDictType(ProperType): "items", "required_keys", "readonly_keys", + "is_closed", "fallback", "extra_items_from", "to_be_mutated", @@ -2968,11 +3051,14 @@ def __init__( fallback: Instance, line: int = -1, column: int = -1, + *, + is_closed: bool = False, ) -> None: super().__init__(line, column) self.items = items self.required_keys = required_keys self.readonly_keys = readonly_keys + self.is_closed = is_closed self.fallback = fallback self.can_be_true = len(self.items) > 0 self.can_be_false = len(self.required_keys) == 0 @@ -2989,6 +3075,7 @@ def __hash__(self) -> int: self.fallback, frozenset(self.required_keys), frozenset(self.readonly_keys), + self.is_closed, ) ) @@ -3006,6 +3093,7 @@ def __eq__(self, other: object) -> bool: and self.fallback == other.fallback and self.required_keys == other.required_keys and self.readonly_keys == other.readonly_keys + and self.is_closed == other.is_closed ) def serialize(self) -> JsonDict: @@ -3015,6 +3103,7 @@ def serialize(self) -> JsonDict: "required_keys": sorted(self.required_keys), "readonly_keys": sorted(self.readonly_keys), "fallback": self.fallback.serialize(), + "is_closed": self.is_closed, } @classmethod @@ -3025,6 +3114,7 @@ def deserialize(cls, data: JsonDict) -> TypedDictType: set(data["required_keys"]), set(data["readonly_keys"]), Instance.deserialize(data["fallback"]), + is_closed=bool(data["is_closed"]), ) def write(self, data: WriteBuffer) -> None: @@ -3033,6 +3123,7 @@ def write(self, data: WriteBuffer) -> None: write_type_map(data, self.items) write_str_list(data, sorted(self.required_keys)) write_str_list(data, sorted(self.readonly_keys)) + write_bool(data, self.is_closed) write_tag(data, END_TAG) @classmethod @@ -3040,7 +3131,11 @@ def read(cls, data: ReadBuffer) -> TypedDictType: assert read_tag(data) == INSTANCE fallback = Instance.read(data) ret = TypedDictType( - read_type_map(data), set(read_str_list(data)), set(read_str_list(data)), fallback + read_type_map(data), + set(read_str_list(data)), + set(read_str_list(data)), + fallback, + is_closed=read_bool(data), ) assert read_tag(data) == END_TAG return ret @@ -3066,6 +3161,7 @@ def copy_modified( item_names: list[str] | None = None, required_keys: set[str] | None = None, readonly_keys: set[str] | None = None, + is_closed: bool | None = None, ) -> TypedDictType: if fallback is None: fallback = self.fallback @@ -3077,13 +3173,20 @@ def copy_modified( required_keys = self.required_keys if readonly_keys is None: readonly_keys = self.readonly_keys + if is_closed is None: + is_closed = self.is_closed if item_names is not None: items = {k: v for (k, v) in items.items() if k in item_names} required_keys &= set(item_names) - return TypedDictType(items, required_keys, readonly_keys, fallback, self.line, self.column) - - def names_are_wider_than(self, other: TypedDictType) -> bool: - return len(other.items.keys() - self.items.keys()) == 0 + return TypedDictType( + items, + required_keys, + readonly_keys, + fallback, + self.line, + self.column, + is_closed=is_closed, + ) def zip(self, right: TypedDictType) -> Iterable[tuple[str, Type, Type]]: left = self @@ -3092,15 +3195,28 @@ def zip(self, right: TypedDictType) -> Iterable[tuple[str, Type, Type]]: if right_item_type is not None: yield (item_name, left_item_type, right_item_type) - def zipall(self, right: TypedDictType) -> Iterable[tuple[str, Type | None, Type | None]]: + def item(self, item_name: str) -> TypedDictItem: + item_type = self.items.get(item_name) + if item_type is not None: + is_required = item_name in self.required_keys + is_readonly = item_name in self.readonly_keys + elif self.is_closed: + item_type = UninhabitedType() + is_required = False + is_readonly = False + else: + is_required = False + is_readonly = True + return TypedDictItem(item_type, is_required, is_readonly) + + def zipall(self, right: TypedDictType) -> Iterable[tuple[str, TypedDictItem, TypedDictItem]]: left = self - for item_name, left_item_type in left.items.items(): - right_item_type = right.items.get(item_name) - yield (item_name, left_item_type, right_item_type) - for item_name, right_item_type in right.items.items(): + for item_name in left.items: + yield (item_name, left.item(item_name), right.item(item_name)) + for item_name in right.items: if item_name in left.items: continue - yield (item_name, None, right_item_type) + yield (item_name, left.item(item_name), right.item(item_name)) class RawExpressionType(ProperType): @@ -3572,11 +3688,12 @@ def deserialize(cls, data: JsonDict) -> Type: def write(self, data: WriteBuffer) -> None: write_tag(data, TYPE_TYPE) self.item.write(data) + write_bool(data, self.is_type_form) write_tag(data, END_TAG) @classmethod def read(cls, data: ReadBuffer) -> Type: - ret = TypeType.make_normalized(read_type(data)) + ret = TypeType.make_normalized(read_type(data), is_type_form=read_bool(data)) assert read_tag(data) == END_TAG return ret @@ -3940,6 +4057,8 @@ def item_str(name: str, typ: str) -> str: + ", ".join(item_str(name, typ.accept(self)) for name, typ in t.items.items()) + "}" ) + if t.is_closed: + s += ", closed=True" prefix = "" if t.fallback and t.fallback.type: if t.fallback.type.fullname not in TPDICT_FB_NAMES: @@ -4190,12 +4309,12 @@ def find_unpack_in_list(items: Sequence[Type]) -> int | None: # Funky code here avoids mypyc narrowing the type of unpack_index. old_index = unpack_index assert old_index is None - # Don't return so that we can also sanity check there is only one. + # Don't return so that we can also sanity-check there is only one. unpack_index = i return unpack_index -def flatten_nested_tuples(types: Iterable[Type]) -> list[Type]: +def flatten_nested_tuples(types: Iterable[Type], handle_recursive: bool = True) -> list[Type]: """Recursively flatten TupleTypes nested with Unpack. For example this will transform @@ -4209,7 +4328,12 @@ def flatten_nested_tuples(types: Iterable[Type]) -> list[Type]: res.append(typ) continue p_type = get_proper_type(typ.type) - if not isinstance(p_type, TupleType): + if ( + not isinstance(p_type, TupleType) + or not handle_recursive + and isinstance(typ.type, TypeAliasType) + and typ.type.is_recursive + ): res.append(typ) continue if isinstance(typ.type, TypeAliasType): diff --git a/mypy/typeshed/stdlib/VERSIONS b/mypy/typeshed/stdlib/VERSIONS index e26bb8da2574e..96eb98131db63 100644 --- a/mypy/typeshed/stdlib/VERSIONS +++ b/mypy/typeshed/stdlib/VERSIONS @@ -23,7 +23,6 @@ _ast: 3.0- _asyncio: 3.0- _bisect: 3.0- _blake2: 3.6- -_bootlocale: 3.4-3.9 _bz2: 3.3- _codecs: 3.0- _collections_abc: 3.3- @@ -61,6 +60,7 @@ _py_abc: 3.7- _pydecimal: 3.5- _queue: 3.7- _random: 3.0- +_remote_debugging: 3.15- _sitebuiltins: 3.4- _socket: 3.0- # present in 3.0 at runtime, but not in typeshed _sqlite3: 3.0- @@ -143,7 +143,6 @@ difflib: 3.0- dis: 3.0- distutils: 3.0-3.11 distutils.command.bdist_msi: 3.0-3.10 -distutils.command.bdist_wininst: 3.0-3.9 doctest: 3.0- email: 3.0- encodings: 3.0- @@ -160,7 +159,6 @@ fcntl: 3.0- filecmp: 3.0- fileinput: 3.0- fnmatch: 3.0- -formatter: 3.0-3.9 fractions: 3.0- ftplib: 3.0- functools: 3.0- @@ -211,6 +209,7 @@ mailbox: 3.0- mailcap: 3.0-3.12 marshal: 3.0- math: 3.0- +math.integer: 3.15- mimetypes: 3.0- mmap: 3.0- modulefinder: 3.0- @@ -231,7 +230,6 @@ operator: 3.0- optparse: 3.0- os: 3.0- ossaudiodev: 3.0-3.12 -parser: 3.0-3.9 pathlib: 3.4- pathlib.types: 3.14- pdb: 3.0- @@ -246,6 +244,7 @@ posix: 3.0- posixpath: 3.0- pprint: 3.0- profile: 3.0- +profiling: 3.15- pstats: 3.0- pty: 3.0- pwd: 3.0- @@ -280,9 +279,9 @@ socket: 3.0- socketserver: 3.0- spwd: 3.0-3.12 sqlite3: 3.0- -sre_compile: 3.0- -sre_constants: 3.0- -sre_parse: 3.0- +sre_compile: 3.0-3.14 +sre_constants: 3.0-3.14 +sre_parse: 3.0-3.14 ssl: 3.0- stat: 3.0- statistics: 3.4- @@ -292,9 +291,9 @@ stringprep: 3.0- struct: 3.0- subprocess: 3.0- sunau: 3.0-3.12 -symbol: 3.0-3.9 symtable: 3.0- sys: 3.0- +sys.__jit: 3.14- # Similar to sys._monitoring sys._monitoring: 3.12- # Doesn't actually exist. See comments in the stub. sysconfig: 3.0- syslog: 3.0- @@ -339,6 +338,7 @@ wsgiref: 3.0- wsgiref.types: 3.11- xdrlib: 3.0-3.12 xml: 3.0- +xml.utils: 3.15- xmlrpc: 3.0- xxlimited: 3.2- zipapp: 3.5- diff --git a/mypy/typeshed/stdlib/_ast.pyi b/mypy/typeshed/stdlib/_ast.pyi index d8d5a1829991e..fd89973aefe65 100644 --- a/mypy/typeshed/stdlib/_ast.pyi +++ b/mypy/typeshed/stdlib/_ast.pyi @@ -58,6 +58,15 @@ from ast import ( LShift as LShift, Lt as Lt, LtE as LtE, + Match as Match, + MatchAs as MatchAs, + MatchClass as MatchClass, + MatchMapping as MatchMapping, + MatchOr as MatchOr, + MatchSequence as MatchSequence, + MatchSingleton as MatchSingleton, + MatchStar as MatchStar, + MatchValue as MatchValue, MatMult as MatMult, Mod as Mod, Module as Module, @@ -101,8 +110,10 @@ from ast import ( expr as expr, expr_context as expr_context, keyword as keyword, + match_case as match_case, mod as mod, operator as operator, + pattern as pattern, stmt as stmt, type_ignore as type_ignore, unaryop as unaryop, @@ -122,21 +133,6 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 11): from ast import TryStar as TryStar -if sys.version_info >= (3, 10): - from ast import ( - Match as Match, - MatchAs as MatchAs, - MatchClass as MatchClass, - MatchMapping as MatchMapping, - MatchOr as MatchOr, - MatchSequence as MatchSequence, - MatchSingleton as MatchSingleton, - MatchStar as MatchStar, - MatchValue as MatchValue, - match_case as match_case, - pattern as pattern, - ) - PyCF_ALLOW_TOP_LEVEL_AWAIT: Final = 8192 PyCF_ONLY_AST: Final = 1024 PyCF_TYPE_COMMENTS: Final = 4096 diff --git a/mypy/typeshed/stdlib/_asyncio.pyi b/mypy/typeshed/stdlib/_asyncio.pyi index f43178e4d7258..1e36b515d408a 100644 --- a/mypy/typeshed/stdlib/_asyncio.pyi +++ b/mypy/typeshed/stdlib/_asyncio.pyi @@ -3,8 +3,8 @@ from asyncio.events import AbstractEventLoop from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable from contextvars import Context from types import FrameType, GenericAlias -from typing import Any, Literal, TextIO, TypeVar -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, Literal, TextIO, TypeAlias, TypeVar +from typing_extensions import Self, disjoint_base _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) @@ -16,10 +16,12 @@ class Future(Awaitable[_T], Iterable[_T]): @property def _exception(self) -> BaseException | None: ... _blocking: bool + @property def _log_traceback(self) -> bool: ... @_log_traceback.setter def _log_traceback(self, val: Literal[False]) -> None: ... + _asyncio_future_blocking: bool # is a part of duck-typing contract for `Future` def __init__(self, *, loop: AbstractEventLoop | None = None) -> None: ... def __del__(self) -> None: ... @@ -97,7 +99,7 @@ class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportIn def get_event_loop() -> AbstractEventLoop: ... def get_running_loop() -> AbstractEventLoop: ... def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ... -def _get_running_loop() -> AbstractEventLoop: ... +def _get_running_loop() -> AbstractEventLoop | None: ... def _register_task(task: Task[Any]) -> None: ... def _unregister_task(task: Task[Any]) -> None: ... def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... diff --git a/mypy/typeshed/stdlib/_bisect.pyi b/mypy/typeshed/stdlib/_bisect.pyi index 5ac43b3e06414..b87dd8f3fb871 100644 --- a/mypy/typeshed/stdlib/_bisect.pyi +++ b/mypy/typeshed/stdlib/_bisect.pyi @@ -1,140 +1,103 @@ -import sys from _typeshed import SupportsGetItem, SupportsLenAndGetItem, SupportsRichComparisonT from collections.abc import Callable, MutableSequence from typing import TypeVar, overload _T = TypeVar("_T") -if sys.version_info >= (3, 10): - @overload - def bisect_left( - a: SupportsLenAndGetItem[SupportsRichComparisonT], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: None = None, - ) -> int: ... - @overload - def bisect_left( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int, *, key: None = None - ) -> int: ... - @overload - def bisect_left( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: None = None - ) -> int: ... - @overload - def bisect_left( - a: SupportsLenAndGetItem[_T], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: Callable[[_T], SupportsRichComparisonT], - ) -> int: ... - @overload - def bisect_left( - a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int, hi: int, *, key: Callable[[_T], SupportsRichComparisonT] - ) -> int: ... - @overload - def bisect_left( - a: SupportsGetItem[int, _T], - x: SupportsRichComparisonT, - lo: int = 0, - *, - hi: int, - key: Callable[[_T], SupportsRichComparisonT], - ) -> int: ... - @overload - def bisect_right( - a: SupportsLenAndGetItem[SupportsRichComparisonT], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: None = None, - ) -> int: ... - @overload - def bisect_right( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int, *, key: None = None - ) -> int: ... - @overload - def bisect_right( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: None = None - ) -> int: ... - @overload - def bisect_right( - a: SupportsLenAndGetItem[_T], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: Callable[[_T], SupportsRichComparisonT], - ) -> int: ... - @overload - def bisect_right( - a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int, hi: int, *, key: Callable[[_T], SupportsRichComparisonT] - ) -> int: ... - @overload - def bisect_right( - a: SupportsGetItem[int, _T], - x: SupportsRichComparisonT, - lo: int = 0, - *, - hi: int, - key: Callable[[_T], SupportsRichComparisonT], - ) -> int: ... - @overload - def insort_left( - a: MutableSequence[SupportsRichComparisonT], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: None = None, - ) -> None: ... - @overload - def insort_left( - a: MutableSequence[_T], x: _T, lo: int = 0, hi: int | None = None, *, key: Callable[[_T], SupportsRichComparisonT] - ) -> None: ... - @overload - def insort_right( - a: MutableSequence[SupportsRichComparisonT], - x: SupportsRichComparisonT, - lo: int = 0, - hi: int | None = None, - *, - key: None = None, - ) -> None: ... - @overload - def insort_right( - a: MutableSequence[_T], x: _T, lo: int = 0, hi: int | None = None, *, key: Callable[[_T], SupportsRichComparisonT] - ) -> None: ... +@overload +def bisect_left( + a: SupportsLenAndGetItem[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, +) -> int: ... +@overload +def bisect_left( + a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int, *, key: None = None +) -> int: ... +@overload +def bisect_left( + a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: None = None +) -> int: ... +@overload +def bisect_left( + a: SupportsLenAndGetItem[_T], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: Callable[[_T], SupportsRichComparisonT], +) -> int: ... +@overload +def bisect_left( + a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int, hi: int, *, key: Callable[[_T], SupportsRichComparisonT] +) -> int: ... +@overload +def bisect_left( + a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: Callable[[_T], SupportsRichComparisonT] +) -> int: ... -else: - @overload - def bisect_left( - a: SupportsLenAndGetItem[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None - ) -> int: ... - @overload - def bisect_left(a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int) -> int: ... - @overload - def bisect_left( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int - ) -> int: ... - @overload - def bisect_right( - a: SupportsLenAndGetItem[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None - ) -> int: ... - @overload - def bisect_right(a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int) -> int: ... - @overload - def bisect_right( - a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int - ) -> int: ... - def insort_left( - a: MutableSequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None - ) -> None: ... - def insort_right( - a: MutableSequence[SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, hi: int | None = None - ) -> None: ... +@overload +def bisect_right( + a: SupportsLenAndGetItem[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, +) -> int: ... +@overload +def bisect_right( + a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int, hi: int, *, key: None = None +) -> int: ... +@overload +def bisect_right( + a: SupportsGetItem[int, SupportsRichComparisonT], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: None = None +) -> int: ... +@overload +def bisect_right( + a: SupportsLenAndGetItem[_T], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: Callable[[_T], SupportsRichComparisonT], +) -> int: ... +@overload +def bisect_right( + a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int, hi: int, *, key: Callable[[_T], SupportsRichComparisonT] +) -> int: ... +@overload +def bisect_right( + a: SupportsGetItem[int, _T], x: SupportsRichComparisonT, lo: int = 0, *, hi: int, key: Callable[[_T], SupportsRichComparisonT] +) -> int: ... + +@overload +def insort_left( + a: MutableSequence[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, +) -> None: ... +@overload +def insort_left( + a: MutableSequence[_T], x: _T, lo: int = 0, hi: int | None = None, *, key: Callable[[_T], SupportsRichComparisonT] +) -> None: ... + +@overload +def insort_right( + a: MutableSequence[SupportsRichComparisonT], + x: SupportsRichComparisonT, + lo: int = 0, + hi: int | None = None, + *, + key: None = None, +) -> None: ... +@overload +def insort_right( + a: MutableSequence[_T], x: _T, lo: int = 0, hi: int | None = None, *, key: Callable[[_T], SupportsRichComparisonT] +) -> None: ... diff --git a/mypy/typeshed/stdlib/_bootlocale.pyi b/mypy/typeshed/stdlib/_bootlocale.pyi deleted file mode 100644 index 233d4934f3c6d..0000000000000 --- a/mypy/typeshed/stdlib/_bootlocale.pyi +++ /dev/null @@ -1 +0,0 @@ -def getpreferredencoding(do_setlocale: bool = True) -> str: ... diff --git a/mypy/typeshed/stdlib/_codecs.pyi b/mypy/typeshed/stdlib/_codecs.pyi index 89cb78c33571d..38cf857d05691 100644 --- a/mypy/typeshed/stdlib/_codecs.pyi +++ b/mypy/typeshed/stdlib/_codecs.pyi @@ -2,8 +2,7 @@ import codecs import sys from _typeshed import ReadableBuffer from collections.abc import Callable -from typing import Literal, final, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Literal, TypeAlias, final, overload, type_check_only # This type is not exposed; it is defined in unicodeobject.c # At runtime it calls itself builtins.EncodingMap @@ -17,10 +16,7 @@ _Handler: TypeAlias = Callable[[UnicodeError], tuple[str | bytes, int]] _SearchFunction: TypeAlias = Callable[[str], codecs.CodecInfo | None] def register(search_function: _SearchFunction, /) -> None: ... - -if sys.version_info >= (3, 10): - def unregister(search_function: _SearchFunction, /) -> None: ... - +def unregister(search_function: _SearchFunction, /) -> None: ... def register_error(errors: str, handler: _Handler, /) -> None: ... def lookup_error(name: str, /) -> _Handler: ... @@ -53,6 +49,7 @@ def encode(obj: ReadableBuffer, encoding: _BytesToBytesEncoding, errors: str = " def encode(obj: str, encoding: _StrToStrEncoding, errors: str = "strict") -> str: ... # type: ignore[overload-overlap] @overload def encode(obj: str, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... + @overload def decode(obj: ReadableBuffer, encoding: _BytesToBytesEncoding, errors: str = "strict") -> bytes: ... # type: ignore[overload-overlap] @overload @@ -71,6 +68,7 @@ def decode( def decode(obj: str, encoding: Literal["hex", "hex_codec"], errors: str = "strict") -> bytes: ... @overload def decode(obj: ReadableBuffer, encoding: str = "utf-8", errors: str = "strict") -> str: ... + def lookup(encoding: str, /) -> codecs.CodecInfo: ... def charmap_build(map: str, /) -> _CharMap: ... def ascii_decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... diff --git a/mypy/typeshed/stdlib/_collections_abc.pyi b/mypy/typeshed/stdlib/_collections_abc.pyi index 0fa81662dfbc1..6fc32078532e0 100644 --- a/mypy/typeshed/stdlib/_collections_abc.pyi +++ b/mypy/typeshed/stdlib/_collections_abc.pyi @@ -1,7 +1,7 @@ import sys from abc import abstractmethod from types import MappingProxyType -from typing import ( # noqa: Y022,Y038,UP035,Y057,RUF100 +from typing import ( # noqa: Y022,Y038,UP035,Y057 AbstractSet as Set, AsyncGenerator as AsyncGenerator, AsyncIterable as AsyncIterable, @@ -60,8 +60,9 @@ __all__ = [ "ValuesView", "Sequence", "MutableSequence", - "ByteString", ] +if sys.version_info < (3, 15): + __all__ += ["ByteString"] if sys.version_info >= (3, 12): __all__ += ["Buffer"] @@ -75,16 +76,15 @@ class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented __hash__: ClassVar[None] # type: ignore[assignment] if sys.version_info >= (3, 13): def isdisjoint(self, other: Iterable[_KT_co], /) -> bool: ... - if sys.version_info >= (3, 10): - @property - def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... + + @property + def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... @final class dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]): # undocumented def __reversed__(self) -> Iterator[_VT_co]: ... - if sys.version_info >= (3, 10): - @property - def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... + @property + def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... @final class dict_items(ItemsView[_KT_co, _VT_co]): # undocumented @@ -93,9 +93,9 @@ class dict_items(ItemsView[_KT_co, _VT_co]): # undocumented __hash__: ClassVar[None] # type: ignore[assignment] if sys.version_info >= (3, 13): def isdisjoint(self, other: Iterable[tuple[_KT_co, _VT_co]], /) -> bool: ... - if sys.version_info >= (3, 10): - @property - def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... + + @property + def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ... if sys.version_info >= (3, 12): @runtime_checkable diff --git a/mypy/typeshed/stdlib/_contextvars.pyi b/mypy/typeshed/stdlib/_contextvars.pyi index 0ddeca7882cd1..56440884a1508 100644 --- a/mypy/typeshed/stdlib/_contextvars.pyi +++ b/mypy/typeshed/stdlib/_contextvars.pyi @@ -1,8 +1,8 @@ import sys from collections.abc import Callable, Iterator, Mapping from types import GenericAlias, TracebackType -from typing import Any, ClassVar, Generic, TypeVar, final, overload -from typing_extensions import ParamSpec, Self +from typing import Any, ClassVar, Generic, ParamSpec, TypeVar, final, overload +from typing_extensions import Self _T = TypeVar("_T") _D = TypeVar("_D") @@ -14,15 +14,18 @@ class ContextVar(Generic[_T]): def __new__(cls, name: str) -> Self: ... @overload def __new__(cls, name: str, *, default: _T) -> Self: ... + def __hash__(self) -> int: ... @property def name(self) -> str: ... + @overload def get(self) -> _T: ... @overload def get(self, default: _T, /) -> _T: ... @overload def get(self, default: _D, /) -> _D | _T: ... + def set(self, value: _T, /) -> Token[_T]: ... def reset(self, token: Token[_T], /) -> None: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -49,12 +52,14 @@ def copy_context() -> Context: ... @final class Context(Mapping[ContextVar[Any], Any]): def __init__(self) -> None: ... + @overload def get(self, key: ContextVar[_T], default: None = None, /) -> _T | None: ... @overload def get(self, key: ContextVar[_T], default: _T, /) -> _T: ... @overload def get(self, key: ContextVar[_T], default: _D, /) -> _T | _D: ... + def run(self, callable: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> _T: ... def copy(self) -> Context: ... __hash__: ClassVar[None] # type: ignore[assignment] diff --git a/mypy/typeshed/stdlib/_csv.pyi b/mypy/typeshed/stdlib/_csv.pyi index ea90766afee66..d3702be78c3d5 100644 --- a/mypy/typeshed/stdlib/_csv.pyi +++ b/mypy/typeshed/stdlib/_csv.pyi @@ -2,8 +2,8 @@ import csv import sys from _typeshed import SupportsWrite from collections.abc import Iterable -from typing import Any, Final, Literal, type_check_only -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, Final, Literal, TypeAlias +from typing_extensions import Self, disjoint_base __version__: Final[str] @@ -47,47 +47,24 @@ class Dialect: strict: bool = False, ) -> Self: ... -if sys.version_info >= (3, 10): - # This class calls itself _csv.reader. - @disjoint_base - class Reader: - @property - def dialect(self) -> Dialect: ... - line_num: int - def __iter__(self) -> Self: ... - def __next__(self) -> list[str]: ... - - # This class calls itself _csv.writer. - @disjoint_base - class Writer: - @property - def dialect(self) -> Dialect: ... - if sys.version_info >= (3, 13): - def writerow(self, row: Iterable[Any], /) -> Any: ... - def writerows(self, rows: Iterable[Iterable[Any]], /) -> None: ... - else: - def writerow(self, row: Iterable[Any]) -> Any: ... - def writerows(self, rows: Iterable[Iterable[Any]]) -> None: ... - - # For the return types below. - # These aliases can be removed when typeshed drops support for 3.9. - _reader = Reader - _writer = Writer -else: - # This class is not exposed. It calls itself _csv.reader. - @type_check_only - class _reader: - @property - def dialect(self) -> Dialect: ... - line_num: int - def __iter__(self) -> Self: ... - def __next__(self) -> list[str]: ... +# This class calls itself _csv.reader. +@disjoint_base +class Reader: + @property + def dialect(self) -> Dialect: ... + line_num: int + def __iter__(self) -> Self: ... + def __next__(self) -> list[str]: ... - # This class is not exposed. It calls itself _csv.writer. - @type_check_only - class _writer: - @property - def dialect(self) -> Dialect: ... +# This class calls itself _csv.writer. +@disjoint_base +class Writer: + @property + def dialect(self) -> Dialect: ... + if sys.version_info >= (3, 13): + def writerow(self, row: Iterable[Any], /) -> Any: ... + def writerows(self, rows: Iterable[Iterable[Any]], /) -> None: ... + else: def writerow(self, row: Iterable[Any]) -> Any: ... def writerows(self, rows: Iterable[Iterable[Any]]) -> None: ... @@ -104,7 +81,7 @@ def writer( lineterminator: str = "\r\n", quoting: _QuotingType = 0, strict: bool = False, -) -> _writer: ... +) -> Writer: ... def reader( iterable: Iterable[str], /, @@ -118,7 +95,7 @@ def reader( lineterminator: str = "\r\n", quoting: _QuotingType = 0, strict: bool = False, -) -> _reader: ... +) -> Reader: ... def register_dialect( name: str, /, diff --git a/mypy/typeshed/stdlib/_ctypes.pyi b/mypy/typeshed/stdlib/_ctypes.pyi index 0e02092a361c9..1efb119263ef1 100644 --- a/mypy/typeshed/stdlib/_ctypes.pyi +++ b/mypy/typeshed/stdlib/_ctypes.pyi @@ -6,8 +6,8 @@ from abc import abstractmethod from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from ctypes import CDLL, ArgumentError as ArgumentError, c_void_p from types import GenericAlias -from typing import Any, ClassVar, Final, Generic, Literal, SupportsIndex, TypeVar, final, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, ClassVar, Final, Generic, Literal, SupportsIndex, TypeAlias, TypeVar, final, overload, type_check_only +from typing_extensions import Self _T = TypeVar("_T") _CT = TypeVar("_CT", bound=_CData) @@ -127,14 +127,17 @@ class _PyCPointerType(_CTypeBaseType): class _Pointer(_PointerLike, _CData, Generic[_CT], metaclass=_PyCPointerType): _type_: type[_CT] contents: _CT + @overload def __init__(self) -> None: ... @overload def __init__(self, arg: _CT) -> None: ... + @overload def __getitem__(self, key: int, /) -> Any: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> list[Any]: ... + def __setitem__(self, key: int, value: Any, /) -> None: ... if sys.version_info < (3, 14): @@ -142,6 +145,7 @@ if sys.version_info < (3, 14): def POINTER(type: None, /) -> type[c_void_p]: ... @overload def POINTER(type: type[_CT], /) -> type[_Pointer[_CT]]: ... + def pointer(obj: _CT, /) -> _Pointer[_CT]: ... # This class is not exposed. It calls itself _ctypes.CArgObject. @@ -177,6 +181,7 @@ class CFuncPtr(_PointerLike, _CData, metaclass=_PyCFuncPtrType): errcheck: _ECT # Abstract attribute that must be defined on subclasses _flags_: ClassVar[int] + @overload def __new__(cls) -> Self: ... @overload @@ -209,10 +214,12 @@ if sys.version_info >= (3, 14): bit_offset: int bit_size: int is_anonymous: bool + @overload def __get__(self, instance: None, owner: builtins.type[Any] | None = None, /) -> Self: ... @overload def __get__(self, instance: Any, owner: builtins.type[Any] | None = None, /) -> _GetT: ... + def __set__(self, instance: Any, value: _SetT, /) -> None: ... _CField = CField @@ -223,16 +230,11 @@ else: class _CField(Generic[_CT, _GetT, _SetT]): offset: int size: int - if sys.version_info >= (3, 10): - @overload - def __get__(self, instance: None, owner: type[Any] | None = None, /) -> Self: ... - @overload - def __get__(self, instance: Any, owner: type[Any] | None = None, /) -> _GetT: ... - else: - @overload - def __get__(self, instance: None, owner: type[Any] | None, /) -> Self: ... - @overload - def __get__(self, instance: Any, owner: type[Any] | None, /) -> _GetT: ... + + @overload + def __get__(self, instance: None, owner: type[Any] | None = None, /) -> Self: ... + @overload + def __get__(self, instance: Any, owner: type[Any] | None = None, /) -> _GetT: ... def __set__(self, instance: Any, value: _SetT, /) -> None: ... @@ -315,11 +317,13 @@ class Array(_CData, Generic[_CT], metaclass=_PyCArrayType): def _length_(self) -> int: ... @_length_.setter def _length_(self, value: int) -> None: ... + @property @abstractmethod def _type_(self) -> type[_CT]: ... @_type_.setter def _type_(self, value: type[_CT]) -> None: ... + raw: bytes # Note: only available if _CT == c_char value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise # TODO: These methods cannot be annotated correctly at the moment. @@ -335,14 +339,17 @@ class Array(_CData, Generic[_CT], metaclass=_PyCArrayType): # This special behavior is not easy to model in a stub, so for now all places where # the array element type would belong are annotated with Any instead. def __init__(self, *args: Any) -> None: ... + @overload def __getitem__(self, key: int, /) -> Any: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> list[Any]: ... + @overload def __setitem__(self, key: int, value: Any, /) -> None: ... @overload def __setitem__(self, key: slice[SupportsIndex | None], value: Iterable[Any], /) -> None: ... + def __iter__(self) -> Iterator[Any]: ... # Can't inherit from Sized because the metaclass conflict between # Sized and _CData prevents using _CDataMeta. diff --git a/mypy/typeshed/stdlib/_curses.pyi b/mypy/typeshed/stdlib/_curses.pyi index d4e4d48f4e20f..fcd0da4c465c7 100644 --- a/mypy/typeshed/stdlib/_curses.pyi +++ b/mypy/typeshed/stdlib/_curses.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import ReadOnlyBuffer, SupportsRead, SupportsWrite from curses import _ncurses_version -from typing import Any, Final, final, overload -from typing_extensions import TypeAlias +from typing import Any, Final, TypeAlias, final, overload # NOTE: This module is ordinarily only available on Unix, but the windows-curses # package makes it available on Windows as well with the same contents. @@ -96,13 +95,12 @@ BUTTON4_PRESSED: Final[int] BUTTON4_RELEASED: Final[int] BUTTON4_TRIPLE_CLICKED: Final[int] # Darwin ncurses doesn't provide BUTTON5_* constants prior to 3.12.10 and 3.13.3 -if sys.version_info >= (3, 10): - if sys.version_info >= (3, 12) or sys.platform != "darwin": - BUTTON5_PRESSED: Final[int] - BUTTON5_RELEASED: Final[int] - BUTTON5_CLICKED: Final[int] - BUTTON5_DOUBLE_CLICKED: Final[int] - BUTTON5_TRIPLE_CLICKED: Final[int] +if sys.version_info >= (3, 12) or sys.platform != "darwin": + BUTTON5_PRESSED: Final[int] + BUTTON5_RELEASED: Final[int] + BUTTON5_CLICKED: Final[int] + BUTTON5_DOUBLE_CLICKED: Final[int] + BUTTON5_TRIPLE_CLICKED: Final[int] BUTTON_ALT: Final[int] BUTTON_CTRL: Final[int] BUTTON_SHIFT: Final[int] @@ -300,9 +298,7 @@ def getsyx() -> tuple[int, int]: ... def getwin(file: SupportsRead[bytes], /) -> window: ... def halfdelay(tenths: int, /) -> None: ... def has_colors() -> bool: ... - -if sys.version_info >= (3, 10): - def has_extended_color_support() -> bool: ... +def has_extended_color_support() -> bool: ... if sys.version_info >= (3, 14): def assume_default_colors(fg: int, bg: int, /) -> None: ... @@ -379,18 +375,22 @@ class error(Exception): ... @final class window: # undocumented encoding: str + @overload def addch(self, ch: _ChType, attr: int = ...) -> None: ... @overload def addch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... + @overload def addnstr(self, str: str, n: int, attr: int = ...) -> None: ... @overload def addnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... + @overload def addstr(self, str: str, attr: int = ...) -> None: ... @overload def addstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... + def attroff(self, attr: int, /) -> None: ... def attron(self, attr: int, /) -> None: ... def attrset(self, attr: int, /) -> None: ... @@ -407,10 +407,12 @@ class window: # undocumented bl: _ChType = ..., br: _ChType = ..., ) -> None: ... + @overload def box(self) -> None: ... @overload def box(self, vertch: _ChType = 0, horch: _ChType = 0) -> None: ... + @overload def chgat(self, attr: int) -> None: ... @overload @@ -419,39 +421,49 @@ class window: # undocumented def chgat(self, y: int, x: int, attr: int) -> None: ... @overload def chgat(self, y: int, x: int, num: int, attr: int) -> None: ... + def clear(self) -> None: ... - def clearok(self, yes: int) -> None: ... + def clearok(self, flag: bool, /) -> None: ... def clrtobot(self) -> None: ... def clrtoeol(self) -> None: ... def cursyncup(self) -> None: ... + @overload def delch(self) -> None: ... @overload def delch(self, y: int, x: int) -> None: ... + def deleteln(self) -> None: ... + @overload def derwin(self, begin_y: int, begin_x: int) -> window: ... @overload def derwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ... + def echochar(self, ch: _ChType, attr: int = 0, /) -> None: ... def enclose(self, y: int, x: int, /) -> bool: ... def erase(self) -> None: ... def getbegyx(self) -> tuple[int, int]: ... def getbkgd(self) -> tuple[int, int]: ... + @overload def getch(self) -> int: ... @overload def getch(self, y: int, x: int) -> int: ... + @overload def get_wch(self) -> int | str: ... @overload def get_wch(self, y: int, x: int) -> int | str: ... + @overload def getkey(self) -> str: ... @overload def getkey(self, y: int, x: int) -> str: ... + def getmaxyx(self) -> tuple[int, int]: ... def getparyx(self) -> tuple[int, int]: ... + @overload def getstr(self) -> bytes: ... @overload @@ -460,89 +472,109 @@ class window: # undocumented def getstr(self, y: int, x: int) -> bytes: ... @overload def getstr(self, y: int, x: int, n: int) -> bytes: ... + def getyx(self) -> tuple[int, int]: ... + @overload def hline(self, ch: _ChType, n: int) -> None: ... @overload def hline(self, y: int, x: int, ch: _ChType, n: int) -> None: ... - def idcok(self, flag: bool) -> None: ... - def idlok(self, yes: bool) -> None: ... - def immedok(self, flag: bool) -> None: ... + + def idcok(self, flag: bool, /) -> None: ... + def idlok(self, flag: bool, /) -> None: ... + def immedok(self, flag: bool, /) -> None: ... + @overload def inch(self) -> int: ... @overload def inch(self, y: int, x: int) -> int: ... + @overload def insch(self, ch: _ChType, attr: int = ...) -> None: ... @overload def insch(self, y: int, x: int, ch: _ChType, attr: int = ...) -> None: ... - def insdelln(self, nlines: int) -> None: ... + + def insdelln(self, nlines: int, /) -> None: ... def insertln(self) -> None: ... + @overload def insnstr(self, str: str, n: int, attr: int = ...) -> None: ... @overload def insnstr(self, y: int, x: int, str: str, n: int, attr: int = ...) -> None: ... + @overload def insstr(self, str: str, attr: int = ...) -> None: ... @overload def insstr(self, y: int, x: int, str: str, attr: int = ...) -> None: ... + @overload def instr(self, n: int = 2047) -> bytes: ... @overload def instr(self, y: int, x: int, n: int = 2047) -> bytes: ... + def is_linetouched(self, line: int, /) -> bool: ... def is_wintouched(self) -> bool: ... - def keypad(self, yes: bool, /) -> None: ... - def leaveok(self, yes: bool) -> None: ... - def move(self, new_y: int, new_x: int) -> None: ... - def mvderwin(self, y: int, x: int) -> None: ... - def mvwin(self, new_y: int, new_x: int) -> None: ... - def nodelay(self, yes: bool) -> None: ... - def notimeout(self, yes: bool) -> None: ... + def keypad(self, flag: bool, /) -> None: ... + def leaveok(self, flag: bool, /) -> None: ... + def move(self, new_y: int, new_x: int, /) -> None: ... + def mvderwin(self, y: int, x: int, /) -> None: ... + def mvwin(self, new_y: int, new_x: int, /) -> None: ... + def nodelay(self, flag: bool, /) -> None: ... + def notimeout(self, flag: bool, /) -> None: ... + @overload def noutrefresh(self) -> None: ... @overload def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... + @overload def overlay(self, destwin: window) -> None: ... @overload def overlay( self, destwin: window, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int ) -> None: ... + @overload def overwrite(self, destwin: window) -> None: ... @overload def overwrite( self, destwin: window, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int ) -> None: ... + def putwin(self, file: SupportsWrite[bytes], /) -> None: ... def redrawln(self, beg: int, num: int, /) -> None: ... def redrawwin(self) -> None: ... + @overload def refresh(self) -> None: ... @overload def refresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ... - def resize(self, nlines: int, ncols: int) -> None: ... + + def resize(self, nlines: int, ncols: int, /) -> None: ... def scroll(self, lines: int = 1) -> None: ... - def scrollok(self, flag: bool) -> None: ... + def scrollok(self, flag: bool, /) -> None: ... def setscrreg(self, top: int, bottom: int, /) -> None: ... def standend(self) -> None: ... def standout(self) -> None: ... + @overload def subpad(self, begin_y: int, begin_x: int) -> window: ... @overload def subpad(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ... + @overload def subwin(self, begin_y: int, begin_x: int) -> window: ... @overload def subwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ... + def syncdown(self) -> None: ... - def syncok(self, flag: bool) -> None: ... + def syncok(self, flag: bool, /) -> None: ... def syncup(self) -> None: ... - def timeout(self, delay: int) -> None: ... + def timeout(self, delay: int, /) -> None: ... def touchline(self, start: int, count: int, changed: bool = True) -> None: ... def touchwin(self) -> None: ... def untouchwin(self) -> None: ... + @overload def vline(self, ch: _ChType, n: int) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/_dbm.pyi b/mypy/typeshed/stdlib/_dbm.pyi index 222c3ffcb246b..29d9b4c2fadca 100644 --- a/mypy/typeshed/stdlib/_dbm.pyi +++ b/mypy/typeshed/stdlib/_dbm.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import ReadOnlyBuffer, StrOrBytesPath from types import TracebackType -from typing import Final, TypeVar, final, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Final, TypeAlias, TypeVar, final, overload, type_check_only +from typing_extensions import Self if sys.platform != "win32": _T = TypeVar("_T") @@ -28,10 +28,12 @@ if sys.platform != "win32": def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> None: ... + @overload def get(self, k: _KeyType, /) -> bytes | None: ... @overload def get(self, k: _KeyType, default: _T, /) -> bytes | _T: ... + def keys(self) -> list[bytes]: ... def setdefault(self, k: _KeyType, default: _ValueType = b"", /) -> bytes: ... # This isn't true, but the class can't be instantiated. See #13024 diff --git a/mypy/typeshed/stdlib/_decimal.pyi b/mypy/typeshed/stdlib/_decimal.pyi index 3cfe8944dfaf4..75d5be277f959 100644 --- a/mypy/typeshed/stdlib/_decimal.pyi +++ b/mypy/typeshed/stdlib/_decimal.pyi @@ -19,13 +19,14 @@ from decimal import ( Underflow as Underflow, _ContextManager, ) -from typing import Final -from typing_extensions import TypeAlias +from typing import Final, TypeAlias _TrapType: TypeAlias = type[DecimalException] __version__: Final[str] __libmpdec_version__: Final[str] +if sys.version_info >= (3, 15): + SPEC_VERSION: Final[str] ROUND_DOWN: Final = "ROUND_DOWN" ROUND_HALF_UP: Final = "ROUND_HALF_UP" diff --git a/mypy/typeshed/stdlib/_frozen_importlib.pyi b/mypy/typeshed/stdlib/_frozen_importlib.pyi index 58db64a016f34..172da4522d8ff 100644 --- a/mypy/typeshed/stdlib/_frozen_importlib.pyi +++ b/mypy/typeshed/stdlib/_frozen_importlib.pyi @@ -74,16 +74,11 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader) "The module spec is now used by the import machinery to generate a module repr." ) def module_repr(module: types.ModuleType) -> str: ... - if sys.version_info >= (3, 10): - @staticmethod - def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... - @staticmethod - def exec_module(module: types.ModuleType) -> None: ... - else: - @classmethod - def create_module(cls, spec: ModuleSpec) -> types.ModuleType | None: ... - @classmethod - def exec_module(cls, module: types.ModuleType) -> None: ... + + @staticmethod + def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... + @staticmethod + def exec_module(module: types.ModuleType) -> None: ... class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): # MetaPathFinder @@ -113,12 +108,8 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader): "The module spec is now used by the import machinery to generate a module repr." ) def module_repr(m: types.ModuleType) -> str: ... - if sys.version_info >= (3, 10): - @staticmethod - def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... - else: - @classmethod - def create_module(cls, spec: ModuleSpec) -> types.ModuleType | None: ... + @staticmethod + def create_module(spec: ModuleSpec) -> types.ModuleType | None: ... @staticmethod def exec_module(module: types.ModuleType) -> None: ... diff --git a/mypy/typeshed/stdlib/_frozen_importlib_external.pyi b/mypy/typeshed/stdlib/_frozen_importlib_external.pyi index 660cee6e84ec8..907e8def93806 100644 --- a/mypy/typeshed/stdlib/_frozen_importlib_external.pyi +++ b/mypy/typeshed/stdlib/_frozen_importlib_external.pyi @@ -1,19 +1,16 @@ import _ast -import _io import importlib.abc import importlib.machinery +import importlib.readers import sys import types from _typeshed import ReadableBuffer, StrOrBytesPath, StrPath from _typeshed.importlib import LoaderProtocol -from collections.abc import Callable, Iterable, Iterator, Mapping, MutableSequence, Sequence +from collections.abc import Callable, Iterable, Mapping, MutableSequence, Sequence from importlib.machinery import ModuleSpec from importlib.metadata import DistributionFinder, PathDistribution from typing import Any, Final, Literal, overload -from typing_extensions import Self, deprecated - -if sys.version_info >= (3, 10): - import importlib.readers +from typing_extensions import deprecated if sys.platform == "win32": path_separators: Literal["\\/"] @@ -33,6 +30,7 @@ MAGIC_NUMBER: Final[bytes] def cache_from_source(path: StrPath, debug_override: bool, *, optimization: None = None) -> str: ... @overload def cache_from_source(path: StrPath, debug_override: None = None, *, optimization: Any | None = None) -> str: ... + def source_from_cache(path: StrPath) -> str: ... def decode_source(source_bytes: ReadableBuffer) -> str: ... def spec_from_file_location( @@ -42,6 +40,7 @@ def spec_from_file_location( loader: LoaderProtocol | None = None, submodule_search_locations: list[str] | None = ..., ) -> importlib.machinery.ModuleSpec | None: ... + @deprecated( "Deprecated since Python 3.6. Use site configuration instead. " "Future versions of Python may not enable this finder by default." @@ -58,19 +57,10 @@ class WindowsRegistryFinder(importlib.abc.MetaPathFinder): ) -> ModuleSpec | None: ... class PathFinder(importlib.abc.MetaPathFinder): - if sys.version_info >= (3, 10): - @staticmethod - def invalidate_caches() -> None: ... - else: - @classmethod - def invalidate_caches(cls) -> None: ... - if sys.version_info >= (3, 10): - @staticmethod - def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... - else: - @classmethod - def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... - + @staticmethod + def invalidate_caches() -> None: ... + @staticmethod + def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ... @classmethod def find_spec( cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None @@ -117,14 +107,7 @@ class FileLoader: def get_data(self, path: str) -> bytes: ... def get_filename(self, fullname: str | None = None) -> str: ... def load_module(self, fullname: str | None = None) -> types.ModuleType: ... - if sys.version_info >= (3, 10): - def get_resource_reader(self, name: str | None = None) -> importlib.readers.FileReader: ... - else: - def get_resource_reader(self, name: str | None = None) -> Self | None: ... - def open_resource(self, resource: str) -> _io.FileIO: ... - def resource_path(self, resource: str) -> str: ... - def is_resource(self, name: str) -> bool: ... - def contents(self) -> Iterator[str]: ... + def get_resource_reader(self, name: str | None = None) -> importlib.readers.FileReader: ... class SourceFileLoader(importlib.abc.FileLoader, FileLoader, importlib.abc.SourceLoader, SourceLoader): # type: ignore[misc] # incompatible method arguments in base classes def set_data(self, path: str, data: ReadableBuffer, *, _mode: int = 0o666) -> None: ... @@ -183,24 +166,15 @@ else: def get_code(self, fullname: str) -> types.CodeType: ... def create_module(self, spec: ModuleSpec) -> None: ... def exec_module(self, module: types.ModuleType) -> None: ... - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `exec_module()` instead.") - def load_module(self, fullname: str) -> types.ModuleType: ... - @staticmethod - @deprecated( - "Deprecated since Python 3.4; removed in Python 3.12. " - "The module spec is now used by the import machinery to generate a module repr." - ) - def module_repr(module: types.ModuleType) -> str: ... - def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ... - else: - def load_module(self, fullname: str) -> types.ModuleType: ... - @classmethod - @deprecated( - "Deprecated since Python 3.4; removed in Python 3.12. " - "The module spec is now used by the import machinery to generate a module repr." - ) - def module_repr(cls, module: types.ModuleType) -> str: ... + @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `exec_module()` instead.") + def load_module(self, fullname: str) -> types.ModuleType: ... + @staticmethod + @deprecated( + "Deprecated since Python 3.4; removed in Python 3.12. " + "The module spec is now used by the import machinery to generate a module repr." + ) + def module_repr(module: types.ModuleType) -> str: ... + def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ... if sys.version_info >= (3, 13): class AppleFrameworkLoader(ExtensionFileLoader, importlib.abc.ExecutionLoader): ... diff --git a/mypy/typeshed/stdlib/_gdbm.pyi b/mypy/typeshed/stdlib/_gdbm.pyi index 2cb5fba29dfa1..b7a01a4531205 100644 --- a/mypy/typeshed/stdlib/_gdbm.pyi +++ b/mypy/typeshed/stdlib/_gdbm.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import ReadOnlyBuffer, StrOrBytesPath from types import TracebackType -from typing import TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self if sys.platform != "win32": _T = TypeVar("_T") @@ -32,10 +32,12 @@ if sys.platform != "win32": def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> None: ... + @overload def get(self, k: _KeyType) -> bytes | None: ... @overload def get(self, k: _KeyType, default: _T) -> bytes | _T: ... + def keys(self) -> list[bytes]: ... def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ... # Don't exist at runtime diff --git a/mypy/typeshed/stdlib/_hashlib.pyi b/mypy/typeshed/stdlib/_hashlib.pyi index 03c1eef3be3ff..b98edc5757c39 100644 --- a/mypy/typeshed/stdlib/_hashlib.pyi +++ b/mypy/typeshed/stdlib/_hashlib.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Callable from types import ModuleType -from typing import AnyStr, Protocol, final, overload, type_check_only -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import AnyStr, Protocol, TypeAlias, final, overload, type_check_only +from typing_extensions import Self, disjoint_base _DigestMod: TypeAlias = str | Callable[[], _HashObject] | ModuleType | None @@ -35,8 +35,7 @@ class HASH: def hexdigest(self) -> str: ... def update(self, obj: ReadableBuffer, /) -> None: ... -if sys.version_info >= (3, 10): - class UnsupportedDigestmodError(ValueError): ... +class UnsupportedDigestmodError(ValueError): ... class HASHXOF(HASH): def digest(self, length: int) -> bytes: ... # type: ignore[override] @@ -59,8 +58,9 @@ class HMAC: def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ... @overload def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ... + def get_fips_mode() -> int: ... -def hmac_new(key: bytes | bytearray, msg: ReadableBuffer = b"", digestmod: _DigestMod = None) -> HMAC: ... +def hmac_new(key: ReadableBuffer, msg: ReadableBuffer = b"", digestmod: _DigestMod = None) -> HMAC: ... if sys.version_info >= (3, 13): def new( @@ -118,7 +118,7 @@ else: def openssl_shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... def openssl_shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ... -def hmac_digest(key: bytes | bytearray, msg: ReadableBuffer, digest: str) -> bytes: ... +def hmac_digest(key: ReadableBuffer, msg: ReadableBuffer, digest: str) -> bytes: ... def pbkdf2_hmac( hash_name: str, password: ReadableBuffer, salt: ReadableBuffer, iterations: int, dklen: int | None = None ) -> bytes: ... diff --git a/mypy/typeshed/stdlib/_heapq.pyi b/mypy/typeshed/stdlib/_heapq.pyi index 4d7d6aba32418..7664b826ae0b5 100644 --- a/mypy/typeshed/stdlib/_heapq.pyi +++ b/mypy/typeshed/stdlib/_heapq.pyi @@ -4,7 +4,7 @@ from typing import Final __about__: Final[str] -def heapify(heap: list[_T], /) -> None: ... +def heapify(heap: list[_T], /) -> None: ... # To work around the fact that list is invariant def heappop(heap: list[_T], /) -> _T: ... def heappush(heap: list[_T], item: _T, /) -> None: ... def heappushpop(heap: list[_T], item: _T, /) -> _T: ... diff --git a/mypy/typeshed/stdlib/_interpqueues.pyi b/mypy/typeshed/stdlib/_interpqueues.pyi index c9323b106f3dc..94605e4c0dda5 100644 --- a/mypy/typeshed/stdlib/_interpqueues.pyi +++ b/mypy/typeshed/stdlib/_interpqueues.pyi @@ -1,5 +1,5 @@ -from typing import Any, Literal, SupportsIndex -from typing_extensions import TypeAlias +import sys +from typing import Any, Literal, SupportsIndex, TypeAlias _UnboundOp: TypeAlias = Literal[1, 2, 3] @@ -7,7 +7,13 @@ class QueueError(RuntimeError): ... class QueueNotFoundError(QueueError): ... def bind(qid: SupportsIndex) -> None: ... -def create(maxsize: SupportsIndex, fmt: SupportsIndex, unboundop: _UnboundOp) -> int: ... + +if sys.version_info >= (3, 15): + def create(maxsize: SupportsIndex, unboundop: SupportsIndex = -1, fallback: SupportsIndex = -1) -> int: ... + +else: + def create(maxsize: SupportsIndex, fmt: SupportsIndex, unboundop: _UnboundOp) -> int: ... + def destroy(qid: SupportsIndex) -> None: ... def get(qid: SupportsIndex) -> tuple[Any, int, _UnboundOp | None]: ... def get_count(qid: SupportsIndex) -> int: ... @@ -15,5 +21,11 @@ def get_maxsize(qid: SupportsIndex) -> int: ... def get_queue_defaults(qid: SupportsIndex) -> tuple[int, _UnboundOp]: ... def is_full(qid: SupportsIndex) -> bool: ... def list_all() -> list[tuple[int, int, _UnboundOp]]: ... -def put(qid: SupportsIndex, obj: Any, fmt: SupportsIndex, unboundop: _UnboundOp) -> None: ... + +if sys.version_info >= (3, 15): + def put(qid: SupportsIndex, obj: Any, unboundop: SupportsIndex = -1, fallback: SupportsIndex = -1) -> None: ... + +else: + def put(qid: SupportsIndex, obj: Any, fmt: SupportsIndex, unboundop: _UnboundOp) -> None: ... + def release(qid: SupportsIndex) -> None: ... diff --git a/mypy/typeshed/stdlib/_interpreters.pyi b/mypy/typeshed/stdlib/_interpreters.pyi index 8e097efad618a..3885669278f51 100644 --- a/mypy/typeshed/stdlib/_interpreters.pyi +++ b/mypy/typeshed/stdlib/_interpreters.pyi @@ -1,7 +1,7 @@ import types from collections.abc import Callable -from typing import Any, Final, Literal, SupportsIndex, TypeVar, overload -from typing_extensions import TypeAlias, disjoint_base +from typing import Any, Final, Literal, SupportsIndex, TypeAlias, TypeVar, overload +from typing_extensions import disjoint_base _R = TypeVar("_R") @@ -47,6 +47,7 @@ def set___main___attrs(id: SupportsIndex, updates: _SharedDict, *, restrict: boo def incref(id: SupportsIndex, *, implieslink: bool = False, restrict: bool = False) -> None: ... def decref(id: SupportsIndex, *, restrict: bool = False) -> None: ... def is_shareable(obj: object) -> bool: ... + @overload def capture_exception(exc: BaseException) -> types.SimpleNamespace: ... @overload diff --git a/mypy/typeshed/stdlib/_io.pyi b/mypy/typeshed/stdlib/_io.pyi index ed8eff2759a98..5e216123be984 100644 --- a/mypy/typeshed/stdlib/_io.pyi +++ b/mypy/typeshed/stdlib/_io.pyi @@ -163,13 +163,39 @@ class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO, Generic[_Buffere def seek(self, target: int, whence: int = 0, /) -> int: ... def truncate(self, pos: int | None = None, /) -> int: ... +@type_check_only +class _BufferedWriterStream(Protocol): + def write(self, b: WriteableBuffer, /) -> int | None: ... + def seek(self, pos: int, whence: int, /) -> int: ... + def tell(self) -> int: ... + def truncate(self, size: int, /) -> int: ... + def flush(self) -> object: ... + def close(self) -> object: ... + @property + def closed(self) -> bool: ... + def writable(self) -> bool: ... + def seekable(self) -> bool: ... + + # The following methods just pass through to the underlying stream. Since + # not all streams support them, they are marked as optional here, and will + # raise an AttributeError if called on a stream that does not support them. + + # @property + # def name(self) -> Any: ... # Type is inconsistent between the various I/O types. + # @property + # def mode(self) -> str: ... + # def fileno(self) -> int: ... + # def isatty(self) -> bool: ... + +_BufferedWriterStreamT = TypeVar("_BufferedWriterStreamT", bound=_BufferedWriterStream, default=_BufferedWriterStream) + @disjoint_base -class BufferedWriter(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes - raw: RawIOBase +class BufferedWriter(BufferedIOBase, _BufferedIOBase, BinaryIO, Generic[_BufferedWriterStreamT]): # type: ignore[misc] # incompatible definitions of writelines in the base classes + raw: _BufferedWriterStreamT if sys.version_info >= (3, 14): - def __init__(self, raw: RawIOBase, buffer_size: int = 131072) -> None: ... + def __init__(self, raw: _BufferedWriterStreamT, buffer_size: int = 131072) -> None: ... else: - def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ... + def __init__(self, raw: _BufferedWriterStreamT, buffer_size: int = 8192) -> None: ... def write(self, buffer: ReadableBuffer, /) -> int: ... def seek(self, target: int, whence: int = 0, /) -> int: ... @@ -190,11 +216,15 @@ class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore def truncate(self, pos: int | None = None, /) -> int: ... @disjoint_base -class BufferedRWPair(BufferedIOBase, _BufferedIOBase, Generic[_BufferedReaderStreamT]): +class BufferedRWPair(BufferedIOBase, _BufferedIOBase, Generic[_BufferedReaderStreamT, _BufferedWriterStreamT]): if sys.version_info >= (3, 14): - def __init__(self, reader: _BufferedReaderStreamT, writer: RawIOBase, buffer_size: int = 131072, /) -> None: ... + def __init__( + self, reader: _BufferedReaderStreamT, writer: _BufferedWriterStreamT, buffer_size: int = 131072, / + ) -> None: ... else: - def __init__(self, reader: _BufferedReaderStreamT, writer: RawIOBase, buffer_size: int = 8192, /) -> None: ... + def __init__( + self, reader: _BufferedReaderStreamT, writer: _BufferedWriterStreamT, buffer_size: int = 8192, / + ) -> None: ... def peek(self, size: int = 0, /) -> bytes: ... @@ -294,8 +324,7 @@ class IncrementalNewlineDecoder: def reset(self) -> None: ... def setstate(self, state: tuple[bytes, int], /) -> None: ... -if sys.version_info >= (3, 10): - @overload - def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ... - @overload - def text_encoding(encoding: _S, stacklevel: int = 2, /) -> _S: ... +@overload +def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ... +@overload +def text_encoding(encoding: _S, stacklevel: int = 2, /) -> _S: ... diff --git a/mypy/typeshed/stdlib/_json.pyi b/mypy/typeshed/stdlib/_json.pyi index 4a77e5be594ab..c6c2c97e83aa9 100644 --- a/mypy/typeshed/stdlib/_json.pyi +++ b/mypy/typeshed/stdlib/_json.pyi @@ -1,3 +1,4 @@ +import sys from collections.abc import Callable from typing import Any, final from typing_extensions import Self @@ -36,6 +37,8 @@ class make_encoder: @final class make_scanner: + if sys.version_info >= (3, 15): + array_hook: Any object_hook: Any object_pairs_hook: Any parse_int: Any @@ -48,4 +51,9 @@ class make_scanner: def encode_basestring(s: str, /) -> str: ... def encode_basestring_ascii(s: str, /) -> str: ... -def scanstring(string: str, end: int, strict: bool = True) -> tuple[str, int]: ... + +if sys.version_info >= (3, 15): + def scanstring(pystr: str, end: int, strict: bool = True, /) -> tuple[str, int]: ... + +else: + def scanstring(string: str, end: int, strict: bool = True) -> tuple[str, int]: ... diff --git a/mypy/typeshed/stdlib/_lsprof.pyi b/mypy/typeshed/stdlib/_lsprof.pyi index 4f6d98b8ffb61..d04c2a74ee07b 100644 --- a/mypy/typeshed/stdlib/_lsprof.pyi +++ b/mypy/typeshed/stdlib/_lsprof.pyi @@ -1,4 +1,3 @@ -import sys from _typeshed import structseq from collections.abc import Callable from types import CodeType @@ -17,8 +16,7 @@ class Profiler: @final class profiler_entry(structseq[Any], tuple[CodeType | str, int, int, float, float, list[profiler_subentry]]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime", "calls") + __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime", "calls") code: CodeType | str callcount: int reccallcount: int @@ -28,8 +26,7 @@ class profiler_entry(structseq[Any], tuple[CodeType | str, int, int, float, floa @final class profiler_subentry(structseq[Any], tuple[CodeType | str, int, int, float, float]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime") + __match_args__: Final = ("code", "callcount", "reccallcount", "totaltime", "inlinetime") code: CodeType | str callcount: int reccallcount: int diff --git a/mypy/typeshed/stdlib/_lzma.pyi b/mypy/typeshed/stdlib/_lzma.pyi index b38dce9fadedf..83cd8fd756b9a 100644 --- a/mypy/typeshed/stdlib/_lzma.pyi +++ b/mypy/typeshed/stdlib/_lzma.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import ReadableBuffer from collections.abc import Mapping, Sequence -from typing import Any, Final, final -from typing_extensions import Self, TypeAlias +from typing import Any, Final, TypeAlias, final +from typing_extensions import Self _FilterChain: TypeAlias = Sequence[Mapping[str, Any]] diff --git a/mypy/typeshed/stdlib/_markupbase.pyi b/mypy/typeshed/stdlib/_markupbase.pyi index 597bd09b700b0..acc3ccac7188c 100644 --- a/mypy/typeshed/stdlib/_markupbase.pyi +++ b/mypy/typeshed/stdlib/_markupbase.pyi @@ -1,6 +1,3 @@ -import sys -from typing import Any - class ParserBase: def reset(self) -> None: ... def getpos(self) -> tuple[int, int]: ... @@ -9,8 +6,5 @@ class ParserBase: def parse_declaration(self, i: int) -> int: ... # undocumented def parse_marked_section(self, i: int, report: bool = True) -> int: ... # undocumented def updatepos(self, i: int, j: int) -> int: ... # undocumented - if sys.version_info < (3, 10): - # Removed from ParserBase: https://bugs.python.org/issue31844 - def error(self, message: str) -> Any: ... # undocumented lineno: int # undocumented offset: int # undocumented diff --git a/mypy/typeshed/stdlib/_msi.pyi b/mypy/typeshed/stdlib/_msi.pyi index edceed51bf9db..e5b408811ee7a 100644 --- a/mypy/typeshed/stdlib/_msi.pyi +++ b/mypy/typeshed/stdlib/_msi.pyi @@ -52,7 +52,7 @@ if sys.platform == "win32": __init__: None # type: ignore[assignment] def UuidCreate() -> str: ... - def FCICreate(cabname: str, files: list[str], /) -> None: ... + def FCICreate(cabname: str, files: list[tuple[str, str]], /) -> None: ... def OpenDatabase(path: str, persist: int, /) -> _Database: ... def CreateRecord(count: int, /) -> _Record: ... diff --git a/mypy/typeshed/stdlib/_operator.pyi b/mypy/typeshed/stdlib/_operator.pyi index 8c705065bde7d..29a6d70572028 100644 --- a/mypy/typeshed/stdlib/_operator.pyi +++ b/mypy/typeshed/stdlib/_operator.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import SupportsGetItem from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence from operator import attrgetter as attrgetter, itemgetter as itemgetter, methodcaller as methodcaller -from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload, type_check_only -from typing_extensions import ParamSpec, TypeAlias, TypeIs +from typing import Any, AnyStr, ParamSpec, Protocol, SupportsAbs, SupportsIndex, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import TypeIs _R = TypeVar("_R") _T = TypeVar("_T") @@ -79,23 +79,28 @@ def xor(a: Any, b: Any, /) -> Any: ... def concat(a: Sequence[_T], b: Sequence[_T], /) -> Sequence[_T]: ... def contains(a: Container[object], b: object, /) -> bool: ... def countOf(a: Iterable[object], b: object, /) -> int: ... + @overload def delitem(a: MutableSequence[Any], b: int, /) -> None: ... @overload def delitem(a: MutableSequence[Any], b: slice[int | None], /) -> None: ... @overload def delitem(a: MutableMapping[_K, Any], b: _K, /) -> None: ... + @overload def getitem(a: Sequence[_T], b: slice[int | None], /) -> Sequence[_T]: ... @overload def getitem(a: SupportsGetItem[_K, _V], b: _K, /) -> _V: ... + def indexOf(a: Iterable[_T], b: _T, /) -> int: ... + @overload def setitem(a: MutableSequence[_T], b: int, c: _T, /) -> None: ... @overload def setitem(a: MutableSequence[_T], b: slice[int | None], c: Sequence[_T], /) -> None: ... @overload def setitem(a: MutableMapping[_K, _V], b: _K, c: _V, /) -> None: ... + def length_hint(obj: object, default: int = 0, /) -> int: ... def iadd(a, b, /): ... def iand(a, b, /): ... diff --git a/mypy/typeshed/stdlib/_pickle.pyi b/mypy/typeshed/stdlib/_pickle.pyi index 4294de4c2c7fe..f8411c6abc4b8 100644 --- a/mypy/typeshed/stdlib/_pickle.pyi +++ b/mypy/typeshed/stdlib/_pickle.pyi @@ -1,8 +1,8 @@ from _typeshed import ReadableBuffer, SupportsWrite from collections.abc import Callable, Iterable, Iterator, Mapping from pickle import PickleBuffer as PickleBuffer -from typing import Any, Protocol, type_check_only -from typing_extensions import TypeAlias, disjoint_base +from typing import Any, Protocol, TypeAlias, type_check_only +from typing_extensions import disjoint_base @type_check_only class _ReadableFileobj(Protocol): @@ -69,10 +69,12 @@ class Pickler: fix_imports: bool = True, buffer_callback: _BufferCallback = None, ) -> None: ... + @property def memo(self) -> PicklerMemoProxy: ... @memo.setter def memo(self, value: PicklerMemoProxy | dict[int, tuple[int, Any]]) -> None: ... + def dump(self, obj: Any, /) -> None: ... def clear_memo(self) -> None: ... @@ -99,10 +101,12 @@ class Unpickler: errors: str = "strict", buffers: Iterable[Any] | None = (), ) -> None: ... + @property def memo(self) -> UnpicklerMemoProxy: ... @memo.setter def memo(self, value: UnpicklerMemoProxy | dict[int, tuple[int, Any]]) -> None: ... + def load(self) -> Any: ... def find_class(self, module_name: str, global_name: str, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/_pydecimal.pyi b/mypy/typeshed/stdlib/_pydecimal.pyi index a6723f749da6d..9fabcc6400969 100644 --- a/mypy/typeshed/stdlib/_pydecimal.pyi +++ b/mypy/typeshed/stdlib/_pydecimal.pyi @@ -45,3 +45,6 @@ __all__ = [ if sys.version_info >= (3, 14): __all__ += ["IEEEContext", "IEEE_CONTEXT_MAX_BITS"] + +if sys.version_info >= (3, 15): + __all__ += ["SPEC_VERSION"] diff --git a/mypy/typeshed/stdlib/_random.pyi b/mypy/typeshed/stdlib/_random.pyi index ac00fdfb7272b..e333b00d1084e 100644 --- a/mypy/typeshed/stdlib/_random.pyi +++ b/mypy/typeshed/stdlib/_random.pyi @@ -1,16 +1,12 @@ -import sys -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import TypeAlias +from typing_extensions import disjoint_base # Actually Tuple[(int,) * 625] _State: TypeAlias = tuple[int, ...] @disjoint_base class Random: - if sys.version_info >= (3, 10): - def __init__(self, seed: object = ..., /) -> None: ... - else: - def __new__(self, seed: object = ..., /) -> Self: ... - + def __init__(self, seed: object = ..., /) -> None: ... def seed(self, n: object = None, /) -> None: ... def getstate(self) -> _State: ... def setstate(self, state: _State, /) -> None: ... diff --git a/mypy/typeshed/stdlib/_remote_debugging.pyi b/mypy/typeshed/stdlib/_remote_debugging.pyi new file mode 100644 index 0000000000000..b001962aad7bf --- /dev/null +++ b/mypy/typeshed/stdlib/_remote_debugging.pyi @@ -0,0 +1,183 @@ +from _typeshed import StrOrBytesPath, structseq +from collections.abc import Callable +from typing import Final, TypeAlias, final +from typing_extensions import Self + +_Location: TypeAlias = tuple[int, int, int, int] | LocationInfo | None +_Frame: TypeAlias = tuple[str, _Location, str, int | None] | FrameInfo +_Stats: TypeAlias = dict[str, int | float] + +PROCESS_VM_READV_SUPPORTED: Final[int] +THREAD_STATUS_GIL_REQUESTED: Final[int] +THREAD_STATUS_HAS_EXCEPTION: Final[int] +THREAD_STATUS_HAS_GIL: Final[int] +THREAD_STATUS_MAIN_THREAD: Final[int] +THREAD_STATUS_ON_CPU: Final[int] +THREAD_STATUS_UNKNOWN: Final[int] + +@final +class LocationInfo(structseq[int], tuple[int, int, int, int]): + __match_args__: Final = ("lineno", "end_lineno", "col_offset", "end_col_offset") + @property + def lineno(self) -> int: ... + @property + def end_lineno(self) -> int: ... + @property + def col_offset(self) -> int: ... + @property + def end_col_offset(self) -> int: ... + +@final +class FrameInfo(structseq[object], tuple[str, _Location, str, int | None]): + __match_args__: Final = ("filename", "location", "funcname", "opcode") + @property + def filename(self) -> str: ... + @property + def location(self) -> _Location: ... + @property + def funcname(self) -> str: ... + @property + def opcode(self) -> int | None: ... + +@final +class CoroInfo(structseq[object], tuple[list[_Frame], int | str]): + __match_args__: Final = ("call_stack", "task_name") + @property + def call_stack(self) -> list[_Frame]: ... + @property + def task_name(self) -> int | str: ... + +@final +class TaskInfo(structseq[object], tuple[int, str, list[CoroInfo], list[CoroInfo]]): + __match_args__: Final = ("task_id", "task_name", "coroutine_stack", "awaited_by") + @property + def task_id(self) -> int: ... + @property + def task_name(self) -> str: ... + @property + def coroutine_stack(self) -> list[CoroInfo]: ... + @property + def awaited_by(self) -> list[CoroInfo]: ... + +@final +class ThreadInfo(structseq[object], tuple[int, int, list[_Frame]]): + __match_args__: Final = ("thread_id", "status", "frame_info") + @property + def thread_id(self) -> int: ... + @property + def status(self) -> int: ... + @property + def frame_info(self) -> list[_Frame]: ... + +@final +class InterpreterInfo(structseq[object], tuple[int, list[ThreadInfo]]): + __match_args__: Final = ("interpreter_id", "threads") + @property + def interpreter_id(self) -> int: ... + @property + def threads(self) -> list[ThreadInfo]: ... + +@final +class AwaitedInfo(structseq[object], tuple[int, list[TaskInfo]]): + __match_args__: Final = ("thread_id", "awaited_by") + @property + def thread_id(self) -> int: ... + @property + def awaited_by(self) -> list[TaskInfo]: ... + +@final +class GCStatsInfo(structseq[object], tuple[int, int, int, int, int, int, int, int, int, float]): + __match_args__: Final = ( + "gen", + "iid", + "ts_start", + "ts_stop", + "collections", + "collected", + "uncollectable", + "candidates", + "heap_size", + "duration", + ) + @property + def gen(self) -> int: ... + @property + def iid(self) -> int: ... + @property + def ts_start(self) -> int: ... + @property + def ts_stop(self) -> int: ... + @property + def collections(self) -> int: ... + @property + def collected(self) -> int: ... + @property + def uncollectable(self) -> int: ... + @property + def candidates(self) -> int: ... + @property + def heap_size(self) -> int: ... + @property + def duration(self) -> float: ... + +@final +class RemoteUnwinder: + def __init__( + self, + pid: int, + *, + all_threads: bool = False, + only_active_thread: bool = False, + mode: int = 0, + debug: bool = False, + skip_non_matching_threads: bool = True, + native: bool = False, + gc: bool = False, + opcodes: bool = False, + cache_frames: bool = False, + stats: bool = False, + ) -> None: ... + def get_stack_trace(self) -> list[InterpreterInfo]: ... + def get_all_awaited_by(self) -> list[AwaitedInfo]: ... + def get_async_stack_trace(self) -> list[AwaitedInfo]: ... + def get_stats(self) -> _Stats: ... + def pause_threads(self) -> bool: ... + def resume_threads(self) -> bool: ... + +@final +class GCMonitor: + def __init__(self, pid: int, *, debug: bool = False) -> None: ... + def get_gc_stats(self, all_interpreters: bool = False) -> list[GCStatsInfo]: ... + +@final +class BinaryWriter: + def __init__( + self, filename: StrOrBytesPath, sample_interval_us: int, start_time_us: int, *, compression: int = 0 + ) -> None: ... + @property + def total_samples(self) -> int: ... + def write_sample(self, stack_frames: list[InterpreterInfo], timestamp_us: int) -> None: ... + def finalize(self) -> None: ... + def close(self) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, exc_type: object = None, exc_val: object = None, exc_tb: object = None) -> bool: ... + def get_stats(self) -> _Stats: ... + +@final +class BinaryReader: + def __init__(self, filename: StrOrBytesPath) -> None: ... + @property + def sample_count(self) -> int: ... + @property + def sample_interval_us(self) -> int: ... + def replay(self, collector: object, progress_callback: Callable[[int, int], object] | None = None) -> int: ... + def get_info(self) -> dict[str, object]: ... + def get_stats(self) -> _Stats: ... + def close(self) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, exc_type: object = None, exc_val: object = None, exc_tb: object = None) -> bool: ... + +def zstd_available() -> bool: ... +def get_child_pids(pid: int, *, recursive: bool = True) -> list[int]: ... +def is_python_process(pid: int) -> bool: ... +def get_gc_stats(pid: int, *, all_interpreters: bool = False) -> list[GCStatsInfo]: ... diff --git a/mypy/typeshed/stdlib/_socket.pyi b/mypy/typeshed/stdlib/_socket.pyi index 918bffc7f9085..4450d46e6027e 100644 --- a/mypy/typeshed/stdlib/_socket.pyi +++ b/mypy/typeshed/stdlib/_socket.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import ReadableBuffer, WriteableBuffer from collections.abc import Iterable from socket import error as error, gaierror as gaierror, herror as herror, timeout as timeout -from typing import Any, Final, SupportsIndex, overload -from typing_extensions import CapsuleType, TypeAlias, disjoint_base +from typing import Any, Final, SupportsIndex, TypeAlias, overload +from typing_extensions import CapsuleType, disjoint_base _CMSG: TypeAlias = tuple[int, int, bytes] _CMSGArg: TypeAlias = tuple[int, int, ReadableBuffer] @@ -11,7 +11,11 @@ _CMSGArg: TypeAlias = tuple[int, int, ReadableBuffer] # Addresses can be either tuples of varying lengths (AF_INET, AF_INET6, # AF_NETLINK, AF_TIPC) or strings/buffers (AF_UNIX). # See getsockaddrarg() in socketmodule.c. -_Address: TypeAlias = tuple[Any, ...] | str | ReadableBuffer +if sys.version_info >= (3, 14): + # A bare int is accepted for Bluetooth HCI device IDs. + _Address: TypeAlias = tuple[Any, ...] | str | ReadableBuffer | int +else: + _Address: TypeAlias = tuple[Any, ...] | str | ReadableBuffer _RetAddress: TypeAlias = Any # ===== Constants ===== @@ -195,7 +199,7 @@ if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "lin if sys.platform == "linux": # Availability: Linux >= 2.6.20, FreeBSD >= 10.1 IPPROTO_UDPLITE: Final[int] -if sys.version_info >= (3, 10) and sys.platform == "linux": +if sys.platform == "linux": IPPROTO_MPTCP: Final[int] IPPORT_RESERVED: Final[int] @@ -218,8 +222,7 @@ IP_MULTICAST_TTL: Final[int] IP_OPTIONS: Final[int] if sys.platform != "linux": IP_RECVDSTADDR: Final[int] -if sys.version_info >= (3, 10): - IP_RECVTOS: Final[int] +IP_RECVTOS: Final[int] IP_TOS: Final[int] IP_TTL: Final[int] if sys.platform != "win32": @@ -231,6 +234,9 @@ if sys.platform != "win32": IP_RETOPTS: Final[int] if sys.version_info >= (3, 13) and sys.platform == "linux": CAN_RAW_ERR_FILTER: Final[int] +if sys.version_info >= (3, 15): + if sys.platform == "win32" or sys.platform == "linux": + IPV6_HDRINCL: Final[int] if sys.version_info >= (3, 14): IP_RECVTTL: Final[int] @@ -248,8 +254,75 @@ if sys.version_info >= (3, 14): TCP_QUICKACK: Final[int] if sys.platform == "linux": + BDADDR_BREDR: Final[int] + BDADDR_LE_PUBLIC: Final[int] + BDADDR_LE_RANDOM: Final[int] + BT_CHANNEL_POLICY: Final[int] + BT_CHANNEL_POLICY_BREDR_ONLY: Final[int] + BT_CHANNEL_POLICY_BREDR_PREFERRED: Final[int] + BT_CODEC: Final[int] + BT_DEFER_SETUP: Final[int] + BT_FLUSHABLE: Final[int] + BT_FLUSHABLE_OFF: Final[int] + BT_FLUSHABLE_ON: Final[int] + BT_ISO_QOS: Final[int] + BT_MODE: Final[int] + BT_MODE_BASIC: Final[int] + BT_MODE_ERTM: Final[int] + BT_MODE_EXT_FLOWCTL: Final[int] + BT_MODE_LE_FLOWCTL: Final[int] + BT_MODE_STREAMING: Final[int] + BT_PHY: Final[int] + BT_PHY_BR_1M_1SLOT: Final[int] + BT_PHY_BR_1M_3SLOT: Final[int] + BT_PHY_BR_1M_5SLOT: Final[int] + BT_PHY_EDR_2M_1SLOT: Final[int] + BT_PHY_EDR_2M_3SLOT: Final[int] + BT_PHY_EDR_2M_5SLOT: Final[int] + BT_PHY_EDR_3M_1SLOT: Final[int] + BT_PHY_EDR_3M_3SLOT: Final[int] + BT_PHY_EDR_3M_5SLOT: Final[int] + BT_PHY_LE_1M_RX: Final[int] + BT_PHY_LE_1M_TX: Final[int] + BT_PHY_LE_2M_RX: Final[int] + BT_PHY_LE_2M_TX: Final[int] + BT_PHY_LE_CODED_RX: Final[int] + BT_PHY_LE_CODED_TX: Final[int] + BT_PKT_STATUS: Final[int] + BT_POWER: Final[int] + BT_POWER_FORCE_ACTIVE_OFF: Final[int] + BT_POWER_FORCE_ACTIVE_ON: Final[int] + BT_RCVMTU: Final[int] + BT_SECURITY: Final[int] + BT_SECURITY_FIPS: Final[int] + BT_SECURITY_HIGH: Final[int] + BT_SECURITY_LOW: Final[int] + BT_SECURITY_MEDIUM: Final[int] + BT_SECURITY_SDP: Final[int] + BT_SNDMTU: Final[int] + BT_VOICE: Final[int] + BT_VOICE_CVSD_16BIT: Final[int] + BT_VOICE_TRANSPARENT: Final[int] + BT_VOICE_TRANSPARENT_16BIT: Final[int] + HCI_CHANNEL_CONTROL: Final[int] + HCI_CHANNEL_LOGGING: Final[int] + HCI_CHANNEL_MONITOR: Final[int] + HCI_CHANNEL_RAW: Final[int] + HCI_CHANNEL_USER: Final[int] + HCI_DEV_NONE: Final[int] IP_FREEBIND: Final[int] IP_RECVORIGDSTADDR: Final[int] + L2CAP_LM: Final[int] + L2CAP_LM_AUTH: Final[int] + L2CAP_LM_ENCRYPT: Final[int] + L2CAP_LM_MASTER: Final[int] + L2CAP_LM_RELIABLE: Final[int] + L2CAP_LM_SECURE: Final[int] + L2CAP_LM_TRUSTED: Final[int] + SOL_BLUETOOTH: Final[int] + SOL_L2CAP: Final[int] + SOL_RFCOMM: Final[int] + SOL_SCO: Final[int] VMADDR_CID_LOCAL: Final[int] if sys.platform != "win32" and sys.platform != "darwin": @@ -343,7 +416,7 @@ if sys.platform != "win32": TCP_NOTSENT_LOWAT: Final[int] if sys.platform != "darwin": TCP_KEEPIDLE: Final[int] -if sys.version_info >= (3, 10) and sys.platform == "darwin": +if sys.platform == "darwin": TCP_KEEPALIVE: Final[int] if sys.version_info >= (3, 11) and sys.platform == "darwin": TCP_CONNECTION_INFO: Final[int] @@ -438,6 +511,35 @@ if sys.platform == "linux": CAN_RAW_JOIN_FILTERS: Final[int] # Availability: Linux >= 2.6.25 CAN_ISOTP: Final[int] + if sys.version_info >= (3, 15): + CAN_ISOTP_CHK_PAD_DATA: Final[int] + CAN_ISOTP_CHK_PAD_LEN: Final[int] + CAN_ISOTP_DEFAULT_EXT_ADDRESS: Final[int] + CAN_ISOTP_DEFAULT_FLAGS: Final[int] + CAN_ISOTP_DEFAULT_FRAME_TXTIME: Final[int] + CAN_ISOTP_DEFAULT_LL_MTU: Final[int] + CAN_ISOTP_DEFAULT_LL_TX_DL: Final[int] + CAN_ISOTP_DEFAULT_LL_TX_FLAGS: Final[int] + CAN_ISOTP_DEFAULT_PAD_CONTENT: Final[int] + CAN_ISOTP_DEFAULT_RECV_BS: Final[int] + CAN_ISOTP_DEFAULT_RECV_STMIN: Final[int] + CAN_ISOTP_DEFAULT_RECV_WFTMAX: Final[int] + CAN_ISOTP_EXTEND_ADDR: Final[int] + CAN_ISOTP_FORCE_RXSTMIN: Final[int] + CAN_ISOTP_FORCE_TXSTMIN: Final[int] + CAN_ISOTP_HALF_DUPLEX: Final[int] + CAN_ISOTP_LL_OPTS: Final[int] + CAN_ISOTP_LISTEN_MODE: Final[int] + CAN_ISOTP_OPTS: Final[int] + CAN_ISOTP_RECV_FC: Final[int] + CAN_ISOTP_RX_EXT_ADDR: Final[int] + CAN_ISOTP_RX_PADDING: Final[int] + CAN_ISOTP_RX_STMIN: Final[int] + CAN_ISOTP_SF_BROADCAST: Final[int] + CAN_ISOTP_TX_PADDING: Final[int] + CAN_ISOTP_TX_STMIN: Final[int] + CAN_ISOTP_WAIT_TX_DONE: Final[int] + SOL_CAN_ISOTP: Final[int] # Availability: Linux >= 5.4 CAN_J1939: Final[int] @@ -757,10 +859,12 @@ class socket: def fileno(self) -> int: ... def getpeername(self) -> _RetAddress: ... def getsockname(self) -> _RetAddress: ... + @overload def getsockopt(self, level: int, optname: int, /) -> int: ... @overload def getsockopt(self, level: int, optname: int, buflen: int, /) -> bytes: ... + def getblocking(self) -> bool: ... def gettimeout(self) -> float | None: ... if sys.platform == "win32": @@ -779,10 +883,12 @@ class socket: def recv_into(self, buffer: WriteableBuffer, nbytes: int = 0, flags: int = 0) -> int: ... def send(self, data: ReadableBuffer, flags: int = 0, /) -> int: ... def sendall(self, data: ReadableBuffer, flags: int = 0, /) -> None: ... + @overload def sendto(self, data: ReadableBuffer, address: _Address, /) -> int: ... @overload def sendto(self, data: ReadableBuffer, flags: int, address: _Address, /) -> int: ... + if sys.platform != "win32": def sendmsg( self, @@ -799,10 +905,12 @@ class socket: def setblocking(self, flag: bool, /) -> None: ... def settimeout(self, value: float | None, /) -> None: ... + @overload def setsockopt(self, level: int, optname: int, value: int | ReadableBuffer, /) -> None: ... @overload def setsockopt(self, level: int, optname: int, value: None, optlen: int, /) -> None: ... + if sys.platform == "win32": def share(self, process_id: int, /) -> bytes: ... diff --git a/mypy/typeshed/stdlib/_sqlite3.pyi b/mypy/typeshed/stdlib/_sqlite3.pyi index 5361584d6b184..b6d96121afa98 100644 --- a/mypy/typeshed/stdlib/_sqlite3.pyi +++ b/mypy/typeshed/stdlib/_sqlite3.pyi @@ -18,8 +18,8 @@ from sqlite3 import ( Warning as Warning, _IsolationLevel, ) -from typing import Any, Final, Literal, TypeVar, overload -from typing_extensions import TypeAlias, deprecated +from typing import Any, Final, Literal, TypeAlias, TypeVar, overload +from typing_extensions import deprecated if sys.version_info >= (3, 11): from sqlite3 import Blob as Blob @@ -69,6 +69,8 @@ SQLITE_SAVEPOINT: Final = 32 SQLITE_SELECT: Final = 21 SQLITE_TRANSACTION: Final = 22 SQLITE_UPDATE: Final = 23 +if sys.version_info >= (3, 15): + SQLITE_KEYWORDS: tuple[str, ...] adapters: dict[tuple[type[Any], type[Any]], _Adapter[Any]] converters: dict[str, _Converter] sqlite_version: str @@ -218,6 +220,7 @@ if sys.version_info >= (3, 11): def adapt(obj: Any, proto: Any, /) -> Any: ... @overload def adapt(obj: Any, proto: Any, alt: _T, /) -> Any | _T: ... + def complete_statement(statement: str) -> bool: ... if sys.version_info >= (3, 12): @@ -259,7 +262,6 @@ if sys.version_info >= (3, 12): uri: bool = False, autocommit: bool = ..., ) -> _ConnectionT: ... - else: @overload def connect( @@ -305,13 +307,5 @@ if sys.version_info < (3, 12): ) def enable_shared_cache(do_enable: int) -> None: ... # undocumented -if sys.version_info >= (3, 10): - def register_adapter(type: type[_T], adapter: _Adapter[_T], /) -> None: ... - def register_converter(typename: str, converter: _Converter, /) -> None: ... - -else: - def register_adapter(type: type[_T], caster: _Adapter[_T], /) -> None: ... - def register_converter(name: str, converter: _Converter, /) -> None: ... - -if sys.version_info < (3, 10): - OptimizedUnicode = str # undocumented +def register_adapter(type: type[_T], adapter: _Adapter[_T], /) -> None: ... +def register_converter(typename: str, converter: _Converter, /) -> None: ... diff --git a/mypy/typeshed/stdlib/_ssl.pyi b/mypy/typeshed/stdlib/_ssl.pyi index e84b24e8f4db7..87023d90e13bb 100644 --- a/mypy/typeshed/stdlib/_ssl.pyi +++ b/mypy/typeshed/stdlib/_ssl.pyi @@ -12,8 +12,8 @@ from ssl import ( SSLWantWriteError as SSLWantWriteError, SSLZeroReturnError as SSLZeroReturnError, ) -from typing import Any, ClassVar, Final, Literal, TypedDict, final, overload, type_check_only -from typing_extensions import NotRequired, Self, TypeAlias, deprecated, disjoint_base +from typing import Any, ClassVar, Final, Literal, TypeAlias, TypedDict, final, overload, type_check_only +from typing_extensions import NotRequired, Self, deprecated, disjoint_base _PasswordType: TypeAlias = Callable[[], str | bytes | bytearray] | str | bytes | bytearray _PCTRTT: TypeAlias = tuple[tuple[str, str], ...] @@ -54,13 +54,12 @@ if sys.version_info < (3, 12): @deprecated("Deprecated since Python 3.6; removed in Python 3.12. Use `ssl.RAND_bytes()` instead.") def RAND_pseudo_bytes(n: int, /) -> tuple[bytes, bool]: ... -if sys.version_info < (3, 10): - @deprecated("Unsupported by OpenSSL since 1.1.1; removed in Python 3.10.") - def RAND_egd(path: str) -> None: ... - def RAND_status() -> bool: ... def get_default_verify_paths() -> tuple[str, str, str, str]: ... +if sys.version_info >= (3, 15): + def get_sigalgs() -> list[str]: ... + if sys.platform == "win32": _EnumRetType: TypeAlias = list[tuple[bytes, str, set[str] | bool]] def enum_certificates(store_name: str) -> _EnumRetType: ... @@ -68,6 +67,7 @@ if sys.platform == "win32": def txt2obj(txt: str, name: bool = False) -> tuple[int, str, str, str]: ... def nid2obj(nid: int, /) -> tuple[int, str, str, str]: ... + @disjoint_base class _SSLContext: check_hostname: bool @@ -78,19 +78,20 @@ class _SSLContext: options: int post_handshake_auth: bool protocol: int - if sys.version_info >= (3, 10): - security_level: int + security_level: int sni_callback: Callable[[SSLObject, str, SSLContext], None | int] | None verify_flags: int verify_mode: int def __new__(cls, protocol: int, /) -> Self: ... def cert_store_stats(self) -> dict[str, int]: ... + @overload def get_ca_certs(self, binary_form: Literal[False] = False) -> list[_PeerCertRetDictType]: ... @overload def get_ca_certs(self, binary_form: Literal[True]) -> list[bytes]: ... @overload def get_ca_certs(self, binary_form: bool = False) -> Any: ... + def get_ciphers(self) -> list[_Cipher]: ... def load_cert_chain( self, certfile: StrOrBytesPath, keyfile: StrOrBytesPath | None = None, password: _PasswordType | None = None @@ -106,6 +107,12 @@ class _SSLContext: def set_ciphers(self, cipherlist: str, /) -> None: ... def set_default_verify_paths(self) -> None: ... def set_ecdh_curve(self, name: str, /) -> None: ... + if sys.version_info >= (3, 15): + def get_groups(self, *, include_aliases: bool = False) -> list[str]: ... + def set_ciphersuites(self, ciphersuites: str, /) -> None: ... + def set_client_sigalgs(self, sigalgslist: str, /) -> None: ... + def set_groups(self, grouplist: str, /) -> None: ... + def set_server_sigalgs(self, sigalgslist: str, /) -> None: ... if sys.version_info >= (3, 13): def set_psk_client_callback(self, callback: Callable[[str | None], tuple[str | None, bytes]] | None) -> None: ... def set_psk_server_callback( @@ -145,18 +152,18 @@ class SSLSession: # # You can find a _ssl._SSLSocket object as the _sslobj attribute of a ssl.SSLSocket object -if sys.version_info >= (3, 10): - @final - class Certificate: - def get_info(self) -> _CertInfo: ... - @overload - def public_bytes(self) -> str: ... - @overload - def public_bytes(self, format: Literal[1] = 1, /) -> str: ... # ENCODING_PEM - @overload - def public_bytes(self, format: Literal[2], /) -> bytes: ... # ENCODING_DER - @overload - def public_bytes(self, format: int, /) -> str | bytes: ... +@final +class Certificate: + def get_info(self) -> _CertInfo: ... + + @overload + def public_bytes(self) -> str: ... + @overload + def public_bytes(self, format: Literal[1] = 1, /) -> str: ... # ENCODING_PEM + @overload + def public_bytes(self, format: Literal[2], /) -> bytes: ... # ENCODING_DER + @overload + def public_bytes(self, format: int, /) -> str | bytes: ... if sys.version_info < (3, 12): err_codes_to_names: dict[tuple[int, int], str] @@ -187,9 +194,8 @@ VERIFY_CRL_CHECK_LEAF: Final = 0x04 VERIFY_CRL_CHECK_CHAIN: Final = 0x0C VERIFY_X509_STRICT: Final = 0x20 VERIFY_X509_TRUSTED_FIRST: Final = 0x8000 -if sys.version_info >= (3, 10): - VERIFY_ALLOW_PROXY_CERTS: Final = 0x40 - VERIFY_X509_PARTIAL_CHAIN: Final = 0x80000 +VERIFY_ALLOW_PROXY_CERTS: Final = 0x40 +VERIFY_X509_PARTIAL_CHAIN: Final = 0x80000 # alert descriptions ALERT_DESCRIPTION_CLOSE_NOTIFY: Final = 0 @@ -258,10 +264,9 @@ HOSTFLAG_NO_PARTIAL_WILDCARDS: Final = 0x4 HOSTFLAG_MULTI_LABEL_WILDCARDS: Final = 0x8 HOSTFLAG_SINGLE_LABEL_SUBDOMAINS: Final = 0x10 -if sys.version_info >= (3, 10): - # certificate file types - ENCODING_PEM: Final = 1 - ENCODING_DER: Final = 2 +# certificate file types +ENCODING_PEM: Final = 1 +ENCODING_DER: Final = 2 # protocol versions PROTO_MINIMUM_SUPPORTED: Final = -2 @@ -279,6 +284,8 @@ HAS_ECDH: Final[bool] HAS_NPN: Final[bool] if sys.version_info >= (3, 13): HAS_PSK: Final[bool] +if sys.version_info >= (3, 15): + HAS_PSK_TLS13: Final[bool] HAS_ALPN: Final[bool] HAS_SSLv2: Final[bool] HAS_SSLv3: Final[bool] diff --git a/mypy/typeshed/stdlib/_struct.pyi b/mypy/typeshed/stdlib/_struct.pyi index a8fac2aea1b00..c13e440e10aa9 100644 --- a/mypy/typeshed/stdlib/_struct.pyi +++ b/mypy/typeshed/stdlib/_struct.pyi @@ -9,6 +9,7 @@ def unpack(format: str | bytes, buffer: ReadableBuffer, /) -> tuple[Any, ...]: . def unpack_from(format: str | bytes, /, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ... def iter_unpack(format: str | bytes, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ... def calcsize(format: str | bytes, /) -> int: ... + @disjoint_base class Struct: @property diff --git a/mypy/typeshed/stdlib/_thread.pyi b/mypy/typeshed/stdlib/_thread.pyi index 1323a55e9aad8..e2d257c9ec0ba 100644 --- a/mypy/typeshed/stdlib/_thread.pyi +++ b/mypy/typeshed/stdlib/_thread.pyi @@ -12,6 +12,7 @@ _Ts = TypeVarTuple("_Ts") error = RuntimeError def _count() -> int: ... + @final class RLock: def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ... @@ -33,6 +34,7 @@ if sys.version_info >= (3, 13): def start_joinable_thread( function: Callable[[], object], handle: _ThreadHandle | None = None, daemon: bool = True ) -> _ThreadHandle: ... + @final class lock: def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ... @@ -71,6 +73,7 @@ else: def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ... @overload def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ... + @overload @deprecated("Obsolete synonym. Use `start_new_thread()` instead.") def start_new(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ... # undocumented @@ -78,12 +81,7 @@ def start_new(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts] @deprecated("Obsolete synonym. Use `start_new_thread()` instead.") def start_new(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ... # undocumented -if sys.version_info >= (3, 10): - def interrupt_main(signum: signal.Signals = signal.SIGINT, /) -> None: ... - -else: - def interrupt_main() -> None: ... - +def interrupt_main(signum: signal.Signals = signal.SIGINT, /) -> None: ... def exit() -> NoReturn: ... @deprecated("Obsolete synonym. Use `exit()` instead.") def exit_thread() -> NoReturn: ... # undocumented @@ -96,10 +94,10 @@ def stack_size(size: int = 0, /) -> int: ... TIMEOUT_MAX: Final[float] def get_native_id() -> int: ... # only available on some platforms + @final class _ExceptHookArgs(structseq[Any], tuple[type[BaseException], BaseException | None, TracebackType | None, Thread | None]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("exc_type", "exc_value", "exc_traceback", "thread") + __match_args__: Final = ("exc_type", "exc_value", "exc_traceback", "thread") @property def exc_type(self) -> type[BaseException]: ... diff --git a/mypy/typeshed/stdlib/_threading_local.pyi b/mypy/typeshed/stdlib/_threading_local.pyi index 5f6acaf840aa1..7d4e61c6f2ec8 100644 --- a/mypy/typeshed/stdlib/_threading_local.pyi +++ b/mypy/typeshed/stdlib/_threading_local.pyi @@ -1,6 +1,6 @@ from threading import RLock -from typing import Any -from typing_extensions import Self, TypeAlias +from typing import Any, TypeAlias +from typing_extensions import Self from weakref import ReferenceType __all__ = ["local"] diff --git a/mypy/typeshed/stdlib/_tkinter.pyi b/mypy/typeshed/stdlib/_tkinter.pyi index 5e46668e08b1c..500345f91e063 100644 --- a/mypy/typeshed/stdlib/_tkinter.pyi +++ b/mypy/typeshed/stdlib/_tkinter.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import FileDescriptorLike, Incomplete from collections.abc import Callable -from typing import Any, ClassVar, Final, Literal, final, overload -from typing_extensions import TypeAlias, deprecated +from typing import Any, ClassVar, Final, Literal, TypeAlias, final, overload +from typing_extensions import deprecated # _tkinter is meant to be only used internally by tkinter, but some tkinter # functions e.g. return _tkinter.Tcl_Obj objects. Tcl_Obj represents a Tcl @@ -91,6 +91,7 @@ class TkappType: def splitlist(self, arg, /) -> tuple[Incomplete, ...]: ... def unsetvar(self, *args, **kwargs): ... + if sys.version_info >= (3, 14): @overload def wantobjects(self) -> Literal[0, 1]: ... @@ -100,6 +101,7 @@ class TkappType: @overload def wantobjects(self, wantobjects: Literal[0, 1] | bool, /) -> None: ... + def willdispatch(self) -> None: ... if sys.version_info >= (3, 12): def gettrace(self, /) -> _TkinterTraceFunc | None: ... diff --git a/mypy/typeshed/stdlib/_typeshed/__init__.pyi b/mypy/typeshed/stdlib/_typeshed/__init__.pyi index c006322b81451..2f65d8aab9c51 100644 --- a/mypy/typeshed/stdlib/_typeshed/__init__.pyi +++ b/mypy/typeshed/stdlib/_typeshed/__init__.pyi @@ -6,7 +6,7 @@ import sys from collections.abc import Awaitable, Callable, Iterable, Iterator, Sequence, Set as AbstractSet, Sized from dataclasses import Field from os import PathLike -from types import FrameType, TracebackType +from types import FrameType, NoneType as NoneType, TracebackType from typing import ( Any, AnyStr, @@ -18,11 +18,11 @@ from typing import ( SupportsFloat, SupportsIndex, SupportsInt, + TypeAlias, TypeVar, - final, overload, ) -from typing_extensions import Buffer, LiteralString, Self as _Self, TypeAlias +from typing_extensions import Buffer, LiteralString, Self as _Self _KT = TypeVar("_KT") _KT_co = TypeVar("_KT_co", covariant=True) @@ -313,6 +313,7 @@ class IndexableBuffer(Buffer, Protocol): class SupportsGetItemBuffer(SliceableBuffer, IndexableBuffer, Protocol): def __contains__(self, x: Any, /) -> bool: ... + @overload def __getitem__(self, slice: slice[SupportsIndex | None], /) -> Sequence[int]: ... @overload @@ -323,15 +324,6 @@ class SizedBuffer(Sized, Buffer, Protocol): ... ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] OptExcInfo: TypeAlias = ExcInfo | tuple[None, None, None] -# stable -if sys.version_info >= (3, 10): - from types import NoneType as NoneType -else: - # Used by type checkers for checks involving None (does not exist at runtime) - @final - class NoneType: - def __bool__(self) -> Literal[False]: ... - # This is an internal CPython type that is like, but subtly different from, a NamedTuple # Subclasses of this type are found in multiple modules. # In typeshed, `structseq` is only ever used as a mixin in combination with a fixed-length `Tuple` @@ -359,10 +351,12 @@ AnyOrLiteralStr = TypeVar("AnyOrLiteralStr", str, bytes, LiteralString) # noqa: StrOrLiteralStr = TypeVar("StrOrLiteralStr", LiteralString, str) # noqa: Y001 # Objects suitable to be passed to sys.setprofile, threading.setprofile, and similar -ProfileFunction: TypeAlias = Callable[[FrameType, str, Any], object] +ProfileFunction: TypeAlias = Callable[[FrameType, Literal["call", "return", "c_call", "c_return", "c_exception"], Any], object] # Objects suitable to be passed to sys.settrace, threading.settrace, and similar -TraceFunction: TypeAlias = Callable[[FrameType, str, Any], TraceFunction | None] +TraceFunction: TypeAlias = Callable[ + [FrameType, Literal["call", "line", "return", "exception", "opcode"], Any], TraceFunction | None +] # experimental # Might not work as expected for pyright, see diff --git a/mypy/typeshed/stdlib/_typeshed/_type_checker_internals.pyi b/mypy/typeshed/stdlib/_typeshed/_type_checker_internals.pyi index feb22aae00732..a8411192a898d 100644 --- a/mypy/typeshed/stdlib/_typeshed/_type_checker_internals.pyi +++ b/mypy/typeshed/stdlib/_typeshed/_type_checker_internals.pyi @@ -5,6 +5,7 @@ import sys import typing_extensions from _collections_abc import dict_items, dict_keys, dict_values +from _typeshed import AnnotationForm from abc import ABCMeta from collections.abc import Awaitable, Generator, Iterable, Mapping from typing import Any, ClassVar, Generic, TypeVar, overload @@ -28,6 +29,10 @@ class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta): if sys.version_info >= (3, 13): __readonly_keys__: ClassVar[frozenset[str]] __mutable_keys__: ClassVar[frozenset[str]] + if sys.version_info >= (3, 15): + # PEP 728 + __closed__: ClassVar[bool | None] + __extra_items__: ClassVar[AnnotationForm] def copy(self) -> typing_extensions.Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature @@ -40,14 +45,17 @@ class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta): def items(self) -> dict_items[str, object]: ... def keys(self) -> dict_keys[str, object]: ... def values(self) -> dict_values[str, object]: ... + @overload def __or__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... @overload def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload def __ror__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... @overload def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of __or__ and __ior__ def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... # type: ignore[misc] @@ -55,6 +63,7 @@ class TypedDictFallback(Mapping[str, object], metaclass=ABCMeta): class NamedTupleFallback(tuple[Any, ...]): _field_defaults: ClassVar[dict[str, Any]] _fields: ClassVar[tuple[str, ...]] + __match_args__: ClassVar[tuple[str, ...]] = ... # __orig_bases__ sometimes exists on <3.12, but not consistently # So we only add it to the stub on 3.12+. if sys.version_info >= (3, 12): @@ -67,6 +76,7 @@ class NamedTupleFallback(tuple[Any, ...]): "Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15" ) def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ... + @classmethod def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ... def _asdict(self) -> dict[str, Any]: ... diff --git a/mypy/typeshed/stdlib/_typeshed/dbapi.pyi b/mypy/typeshed/stdlib/_typeshed/dbapi.pyi index d54fbee57042a..e08a84553dfc0 100644 --- a/mypy/typeshed/stdlib/_typeshed/dbapi.pyi +++ b/mypy/typeshed/stdlib/_typeshed/dbapi.pyi @@ -2,8 +2,7 @@ # https://www.python.org/dev/peps/pep-0249/ from collections.abc import Mapping, Sequence -from typing import Any, Protocol -from typing_extensions import TypeAlias +from typing import Any, Protocol, TypeAlias DBAPITypeCode: TypeAlias = Any | None # Strictly speaking, this should be a Sequence, but the type system does diff --git a/mypy/typeshed/stdlib/_typeshed/wsgi.pyi b/mypy/typeshed/stdlib/_typeshed/wsgi.pyi index 63f204eb889b6..980a24122252e 100644 --- a/mypy/typeshed/stdlib/_typeshed/wsgi.pyi +++ b/mypy/typeshed/stdlib/_typeshed/wsgi.pyi @@ -7,8 +7,7 @@ import sys from _typeshed import OptExcInfo from collections.abc import Callable, Iterable, Iterator -from typing import Any, Protocol -from typing_extensions import TypeAlias +from typing import Any, Protocol, TypeAlias class _Readable(Protocol): def read(self, size: int = ..., /) -> bytes: ... diff --git a/mypy/typeshed/stdlib/_warnings.pyi b/mypy/typeshed/stdlib/_warnings.pyi index 2dbc7b8552813..5f4648259025d 100644 --- a/mypy/typeshed/stdlib/_warnings.pyi +++ b/mypy/typeshed/stdlib/_warnings.pyi @@ -24,7 +24,6 @@ if sys.version_info >= (3, 12): *, skip_file_prefixes: tuple[str, ...] = (), ) -> None: ... - else: @overload def warn(message: str, category: type[Warning] | None = None, stacklevel: int = 1, source: Any | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/_weakrefset.pyi b/mypy/typeshed/stdlib/_weakrefset.pyi index dad1ed7a4fb5c..82ffa4463f488 100644 --- a/mypy/typeshed/stdlib/_weakrefset.pyi +++ b/mypy/typeshed/stdlib/_weakrefset.pyi @@ -13,6 +13,7 @@ class WeakSet(MutableSet[_T]): def __init__(self, data: None = None) -> None: ... @overload def __init__(self, data: Iterable[_T]) -> None: ... + def add(self, item: _T) -> None: ... def discard(self, item: _T) -> None: ... def copy(self) -> Self: ... diff --git a/mypy/typeshed/stdlib/_winapi.pyi b/mypy/typeshed/stdlib/_winapi.pyi index 42efce9bed705..7fd918d9b1e99 100644 --- a/mypy/typeshed/stdlib/_winapi.pyi +++ b/mypy/typeshed/stdlib/_winapi.pyi @@ -29,6 +29,14 @@ if sys.platform == "win32": ERROR_PIPE_CONNECTED: Final = 535 ERROR_SEM_TIMEOUT: Final = 121 + if sys.version_info >= (3, 15): + EVENTLOG_AUDIT_FAILURE: Final = 16 + EVENTLOG_AUDIT_SUCCESS: Final = 8 + EVENTLOG_ERROR_TYPE: Final = 1 + EVENTLOG_INFORMATION_TYPE: Final = 4 + EVENTLOG_SUCCESS: Final = 0 + EVENTLOG_WARNING_TYPE: Final = 2 + FILE_FLAG_FIRST_PIPE_INSTANCE: Final = 0x80000 FILE_FLAG_OVERLAPPED: Final = 0x40000000 @@ -127,22 +135,21 @@ if sys.platform == "win32": WAIT_OBJECT_0: Final = 0 WAIT_TIMEOUT: Final = 258 - if sys.version_info >= (3, 10): - LOCALE_NAME_INVARIANT: Final[str] - LOCALE_NAME_MAX_LENGTH: Final[int] - LOCALE_NAME_SYSTEM_DEFAULT: Final[str] - LOCALE_NAME_USER_DEFAULT: Final[str | None] - - LCMAP_FULLWIDTH: Final[int] - LCMAP_HALFWIDTH: Final[int] - LCMAP_HIRAGANA: Final[int] - LCMAP_KATAKANA: Final[int] - LCMAP_LINGUISTIC_CASING: Final[int] - LCMAP_LOWERCASE: Final[int] - LCMAP_SIMPLIFIED_CHINESE: Final[int] - LCMAP_TITLECASE: Final[int] - LCMAP_TRADITIONAL_CHINESE: Final[int] - LCMAP_UPPERCASE: Final[int] + LOCALE_NAME_INVARIANT: Final[str] + LOCALE_NAME_MAX_LENGTH: Final[int] + LOCALE_NAME_SYSTEM_DEFAULT: Final[str] + LOCALE_NAME_USER_DEFAULT: Final[str | None] + + LCMAP_FULLWIDTH: Final[int] + LCMAP_HALFWIDTH: Final[int] + LCMAP_HIRAGANA: Final[int] + LCMAP_KATAKANA: Final[int] + LCMAP_LINGUISTIC_CASING: Final[int] + LCMAP_LOWERCASE: Final[int] + LCMAP_SIMPLIFIED_CHINESE: Final[int] + LCMAP_TITLECASE: Final[int] + LCMAP_TRADITIONAL_CHINESE: Final[int] + LCMAP_UPPERCASE: Final[int] if sys.version_info >= (3, 12): COPYFILE2_CALLBACK_CHUNK_STARTED: Final = 1 @@ -176,12 +183,14 @@ if sys.platform == "win32": COPY_FILE_DIRECTORY: Final = 0x00000080 def CloseHandle(handle: int, /) -> None: ... + @overload def ConnectNamedPipe(handle: int, overlapped: Literal[True]) -> Overlapped: ... @overload def ConnectNamedPipe(handle: int, overlapped: Literal[False] = False) -> None: ... @overload def ConnectNamedPipe(handle: int, overlapped: bool) -> Overlapped | None: ... + def CreateFile( file_name: str, desired_access: int, @@ -231,6 +240,10 @@ if sys.platform == "win32": ) -> int: ... def ExitProcess(ExitCode: int, /) -> NoReturn: ... def GetACP() -> int: ... + if sys.version_info >= (3, 15): + def DeregisterEventSource(handle: int, /) -> None: ... + def GetOEMCP() -> int: ... + def GetFileType(handle: int) -> int: ... def GetCurrentProcess() -> int: ... def GetExitCodeProcess(process: int, /) -> int: ... @@ -243,9 +256,12 @@ if sys.platform == "win32": ) -> int: ... def OpenProcess(desired_access: int, inherit_handle: bool, process_id: int, /) -> int: ... def PeekNamedPipe(handle: int, size: int = 0, /) -> tuple[int, int] | tuple[bytes, int, int]: ... - if sys.version_info >= (3, 10): - def LCMapStringEx(locale: str, flags: int, src: str) -> str: ... - def UnmapViewOfFile(address: int, /) -> None: ... + def LCMapStringEx(locale: str, flags: int, src: str) -> str: ... + if sys.version_info >= (3, 15): + def RegisterEventSource(unc_server_name: str | None, source_name: str, /) -> int: ... + def ReportEvent(handle: int, type: int, category: int, event_id: int, string: str, /) -> None: ... + + def UnmapViewOfFile(address: int, /) -> None: ... @overload def ReadFile(handle: int, size: int, overlapped: Literal[True]) -> tuple[Overlapped, int]: ... @@ -253,6 +269,7 @@ if sys.platform == "win32": def ReadFile(handle: int, size: int, overlapped: Literal[False] = False) -> tuple[bytes, int]: ... @overload def ReadFile(handle: int, size: int, overlapped: int | bool) -> tuple[Any, int]: ... + def SetNamedPipeHandleState( named_pipe: int, mode: int | None, max_collection_count: int | None, collect_data_timeout: int | None, / ) -> None: ... @@ -261,12 +278,14 @@ if sys.platform == "win32": def WaitForMultipleObjects(handle_seq: Sequence[int], wait_flag: bool, milliseconds: int = 0xFFFFFFFF, /) -> int: ... def WaitForSingleObject(handle: int, milliseconds: int, /) -> int: ... def WaitNamedPipe(name: str, timeout: int, /) -> None: ... + @overload def WriteFile(handle: int, buffer: ReadableBuffer, overlapped: Literal[True]) -> tuple[Overlapped, int]: ... @overload def WriteFile(handle: int, buffer: ReadableBuffer, overlapped: Literal[False] = False) -> tuple[int, int]: ... @overload def WriteFile(handle: int, buffer: ReadableBuffer, overlapped: int | bool) -> tuple[Any, int]: ... + @final class Overlapped: event: int diff --git a/mypy/typeshed/stdlib/_zstd.pyi b/mypy/typeshed/stdlib/_zstd.pyi index e40c7d12b6e77..34b619f9b0b62 100644 --- a/mypy/typeshed/stdlib/_zstd.pyi +++ b/mypy/typeshed/stdlib/_zstd.pyi @@ -1,8 +1,8 @@ from _typeshed import ReadableBuffer from collections.abc import Mapping from compression.zstd import CompressionParameter, DecompressionParameter -from typing import Final, Literal, final -from typing_extensions import Self, TypeAlias +from typing import Final, Literal, TypeAlias, final +from typing_extensions import Self ZSTD_CLEVEL_DEFAULT: Final = 3 ZSTD_DStreamOutSize: Final = 131072 @@ -74,7 +74,7 @@ class ZstdDecompressor: @final class ZstdDict: - def __new__(cls, dict_content: bytes, /, *, is_raw: bool = False) -> Self: ... + def __new__(cls, dict_content: ReadableBuffer, /, *, is_raw: bool = False) -> Self: ... def __len__(self, /) -> int: ... @property def as_digested_dict(self) -> tuple[Self, int]: ... diff --git a/mypy/typeshed/stdlib/abc.pyi b/mypy/typeshed/stdlib/abc.pyi index 7e76abace214e..a43a641489a43 100644 --- a/mypy/typeshed/stdlib/abc.pyi +++ b/mypy/typeshed/stdlib/abc.pyi @@ -2,8 +2,8 @@ import _typeshed import sys from _typeshed import SupportsWrite from collections.abc import Callable -from typing import Any, Literal, TypeVar -from typing_extensions import Concatenate, ParamSpec, deprecated +from typing import Any, Concatenate, Literal, ParamSpec, TypeVar +from typing_extensions import deprecated _T = TypeVar("_T") _R_co = TypeVar("_R_co", covariant=True) @@ -28,6 +28,7 @@ class ABCMeta(type): def register(cls: ABCMeta, subclass: type[_T]) -> type[_T]: ... def abstractmethod(funcobj: _FuncT) -> _FuncT: ... + @deprecated("Deprecated since Python 3.3. Use `@classmethod` stacked on top of `@abstractmethod` instead.") class abstractclassmethod(classmethod[_T, _P, _R_co]): __isabstractmethod__: Literal[True] @@ -46,6 +47,4 @@ class ABC(metaclass=ABCMeta): __slots__ = () def get_cache_token() -> object: ... - -if sys.version_info >= (3, 10): - def update_abstractmethods(cls: type[_T]) -> type[_T]: ... +def update_abstractmethods(cls: type[_T]) -> type[_T]: ... diff --git a/mypy/typeshed/stdlib/aifc.pyi b/mypy/typeshed/stdlib/aifc.pyi index bfe12c6af2b0b..afb9029f710ea 100644 --- a/mypy/typeshed/stdlib/aifc.pyi +++ b/mypy/typeshed/stdlib/aifc.pyi @@ -1,6 +1,6 @@ from types import TracebackType -from typing import IO, Any, Literal, NamedTuple, overload -from typing_extensions import Self, TypeAlias +from typing import IO, Any, Literal, NamedTuple, TypeAlias, overload +from typing_extensions import Self __all__ = ["Error", "open"] diff --git a/mypy/typeshed/stdlib/annotationlib.pyi b/mypy/typeshed/stdlib/annotationlib.pyi index 3679dc29daaa0..c3e843d95d9a8 100644 --- a/mypy/typeshed/stdlib/annotationlib.pyi +++ b/mypy/typeshed/stdlib/annotationlib.pyi @@ -41,13 +41,16 @@ if sys.version_info >= (3, 14): "__cell__", "__owner__", "__stringifier_dict__", + "__resolved_str_cache__", ) __forward_is_argument__: bool __forward_is_class__: bool __forward_module__: str | None + __resolved_str_cache__: str | None def __init__( self, arg: str, *, module: str | None = None, owner: object = None, is_argument: bool = True, is_class: bool = False ) -> None: ... + @overload def evaluate( self, @@ -78,6 +81,7 @@ if sys.version_info >= (3, 14): owner: object = None, format: Format = Format.VALUE, # noqa: Y011 ) -> AnnotationForm: ... + @deprecated("Use `ForwardRef.evaluate()` or `typing.evaluate_forward_ref()` instead.") def _evaluate( self, @@ -91,6 +95,8 @@ if sys.version_info >= (3, 14): def __forward_arg__(self) -> str: ... @property def __forward_code__(self) -> types.CodeType: ... + @property + def __resolved_str__(self) -> str: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __or__(self, other: Any) -> types.UnionType: ... @@ -104,6 +110,7 @@ if sys.version_info >= (3, 14): ) -> AnnotationForm | ForwardRef: ... @overload def call_evaluate_function(evaluate: EvaluateFunc, format: Format, *, owner: object = None) -> AnnotationForm: ... + @overload def call_annotate_function( annotate: AnnotateFunc, format: Literal[Format.STRING], *, owner: object = None @@ -114,7 +121,9 @@ if sys.version_info >= (3, 14): ) -> dict[str, AnnotationForm | ForwardRef]: ... @overload def call_annotate_function(annotate: AnnotateFunc, format: Format, *, owner: object = None) -> dict[str, AnnotationForm]: ... + def get_annotate_from_class_namespace(obj: Mapping[str, object]) -> AnnotateFunc | None: ... + @overload def get_annotations( obj: Any, # any object with __annotations__ or __annotate__ @@ -142,5 +151,6 @@ if sys.version_info >= (3, 14): eval_str: bool = False, format: Format = Format.VALUE, # noqa: Y011 ) -> dict[str, AnnotationForm]: ... + def type_repr(value: object) -> str: ... def annotations_to_string(annotations: SupportsItems[str, object]) -> dict[str, str]: ... diff --git a/mypy/typeshed/stdlib/argparse.pyi b/mypy/typeshed/stdlib/argparse.pyi index 7d4bd1a3a8418..fa22f842de028 100644 --- a/mypy/typeshed/stdlib/argparse.pyi +++ b/mypy/typeshed/stdlib/argparse.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import SupportsWrite, sentinel from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern -from typing import IO, Any, ClassVar, Final, Generic, NewType, NoReturn, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import IO, Any, ClassVar, Final, Generic, NewType, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, deprecated __all__ = [ "ArgumentParser", @@ -93,39 +93,28 @@ class _ActionsContainer: version: str = ..., **kwargs: Any, ) -> Action: ... - if sys.version_info >= (3, 14): - @overload - def add_argument_group( - self, - title: str | None = None, - description: str | None = None, - *, - # argument_default's type must be valid for the arguments in the group - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> _ArgumentGroup: ... - @overload - @deprecated("The `prefix_chars` parameter deprecated since Python 3.14.") - def add_argument_group( - self, - title: str | None = None, - description: str | None = None, - *, - prefix_chars: str, - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> _ArgumentGroup: ... - else: - def add_argument_group( - self, - title: str | None = None, - description: str | None = None, - *, - prefix_chars: str = ..., - # argument_default's type must be valid for the arguments in the group - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> _ArgumentGroup: ... + + @overload + def add_argument_group( + self, + title: str | None = None, + description: str | None = None, + *, + # argument_default's type must be valid for the arguments in the group + argument_default: Any = ..., + conflict_handler: str = ..., + ) -> _ArgumentGroup: ... + @overload + @deprecated("The `prefix_chars` parameter deprecated since Python 3.14.") + def add_argument_group( + self, + title: str | None = None, + description: str | None = None, + *, + prefix_chars: str, + argument_default: Any = ..., + conflict_handler: str = ..., + ) -> _ArgumentGroup: ... def add_mutually_exclusive_group(self, *, required: bool = False) -> _MutuallyExclusiveGroup: ... def _add_action(self, action: _ActionT) -> _ActionT: ... @@ -163,7 +152,28 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): _subparsers: _ArgumentGroup | None # Note: the constructor arguments are also used in _SubParsersAction.add_parser. - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + def __init__( + self, + prog: str | None = None, + usage: str | None = None, + description: str | None = None, + epilog: str | None = None, + parents: Iterable[ArgumentParser] = [], + formatter_class: _FormatterClass = ..., + prefix_chars: str = "-", + fromfile_prefix_chars: str | None = None, + argument_default: Any = None, + conflict_handler: str = "error", + add_help: bool = True, + allow_abbrev: bool = True, + exit_on_error: bool = True, + *, + suggest_on_error: bool = True, + color: bool = True, + ) -> None: ... + + elif sys.version_info >= (3, 14): def __init__( self, prog: str | None = None, @@ -207,6 +217,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def parse_args(self, args: Iterable[str] | None, namespace: _N) -> _N: ... @overload def parse_args(self, *, namespace: _N) -> _N: ... + @overload def add_subparsers( self: _ArgumentParserT, @@ -236,25 +247,35 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): help: str | None = None, metavar: str | None = None, ) -> _SubParsersAction[_ArgumentParserT]: ... + def print_usage(self, file: SupportsWrite[str] | None = None) -> None: ... def print_help(self, file: SupportsWrite[str] | None = None) -> None: ... - def format_usage(self) -> str: ... - def format_help(self) -> str: ... + if sys.version_info >= (3, 15): + def format_usage(self, formatter: HelpFormatter | None = None) -> str: ... + def format_help(self, formatter: HelpFormatter | None = None) -> str: ... + + else: + def format_usage(self) -> str: ... + def format_help(self) -> str: ... + @overload def parse_known_args(self, args: Iterable[str] | None = None, namespace: None = None) -> tuple[Namespace, list[str]]: ... @overload def parse_known_args(self, args: Iterable[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload def parse_known_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... + def convert_arg_line_to_args(self, arg_line: str) -> list[str]: ... def exit(self, status: int = 0, message: str | None = None) -> NoReturn: ... def error(self, message: str) -> NoReturn: ... + @overload def parse_intermixed_args(self, args: Iterable[str] | None = None, namespace: None = None) -> Namespace: ... @overload def parse_intermixed_args(self, args: Iterable[str] | None, namespace: _N) -> _N: ... @overload def parse_intermixed_args(self, *, namespace: _N) -> _N: ... + @overload def parse_known_intermixed_args( self, args: Iterable[str] | None = None, namespace: None = None @@ -263,6 +284,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def parse_known_intermixed_args(self, args: Iterable[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... @overload def parse_known_intermixed_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... + # undocumented def _get_optional_actions(self) -> list[Action]: ... def _get_positional_actions(self) -> list[Action]: ... @@ -286,7 +308,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def _get_values(self, action: Action, arg_strings: list[str]) -> Any: ... def _get_value(self, action: Action, arg_string: str) -> Any: ... def _check_value(self, action: Action, value: Any) -> None: ... - def _get_formatter(self) -> HelpFormatter: ... + if sys.version_info >= (3, 15): + def _get_formatter(self, file: SupportsWrite[str] | None = None) -> HelpFormatter: ... + else: + def _get_formatter(self) -> HelpFormatter: ... + def _print_message(self, message: str, file: SupportsWrite[str] | None = None) -> None: ... class HelpFormatter: @@ -311,7 +337,12 @@ class HelpFormatter: def __init__(self, formatter: HelpFormatter, parent: Self | None, heading: str | None = None) -> None: ... def format_help(self) -> str: ... - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + def __init__( + self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None + ) -> None: ... + + elif sys.version_info >= (3, 14): def __init__( self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = True ) -> None: ... @@ -501,69 +532,43 @@ class Namespace(_AttributeHolder): def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] -if sys.version_info >= (3, 14): - @deprecated("Deprecated since Python 3.14. Open files after parsing arguments instead.") - class FileType: - # undocumented - _mode: str - _bufsize: int - _encoding: str | None - _errors: str | None - def __init__( - self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None - ) -> None: ... - def __call__(self, string: str) -> IO[Any]: ... - -else: - class FileType: - # undocumented - _mode: str - _bufsize: int - _encoding: str | None - _errors: str | None - def __init__( - self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None - ) -> None: ... - def __call__(self, string: str) -> IO[Any]: ... +@deprecated("Deprecated since Python 3.14. Open files after parsing arguments instead.") +class FileType: + # undocumented + _mode: str + _bufsize: int + _encoding: str | None + _errors: str | None + def __init__(self, mode: str = "r", bufsize: int = -1, encoding: str | None = None, errors: str | None = None) -> None: ... + def __call__(self, string: str) -> IO[Any]: ... # undocumented class _ArgumentGroup(_ActionsContainer): title: str | None _group_actions: list[Action] - if sys.version_info >= (3, 14): - @overload - def __init__( - self, - container: _ActionsContainer, - title: str | None = None, - description: str | None = None, - *, - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> None: ... - @overload - @deprecated("Undocumented `prefix_chars` parameter is deprecated since Python 3.14.") - def __init__( - self, - container: _ActionsContainer, - title: str | None = None, - description: str | None = None, - *, - prefix_chars: str, - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> None: ... - else: - def __init__( - self, - container: _ActionsContainer, - title: str | None = None, - description: str | None = None, - *, - prefix_chars: str = ..., - argument_default: Any = ..., - conflict_handler: str = ..., - ) -> None: ... + + @overload + def __init__( + self, + container: _ActionsContainer, + title: str | None = None, + description: str | None = None, + *, + argument_default: Any = ..., + conflict_handler: str = ..., + ) -> None: ... + @overload + @deprecated("Undocumented `prefix_chars` parameter is deprecated since Python 3.14.") + def __init__( + self, + container: _ActionsContainer, + title: str | None = None, + description: str | None = None, + *, + prefix_chars: str, + argument_default: Any = ..., + conflict_handler: str = ..., + ) -> None: ... # undocumented class _MutuallyExclusiveGroup(_ArgumentGroup): diff --git a/mypy/typeshed/stdlib/array.pyi b/mypy/typeshed/stdlib/array.pyi index eb679dd50f722..2c83146edbf00 100644 --- a/mypy/typeshed/stdlib/array.pyi +++ b/mypy/typeshed/stdlib/array.pyi @@ -2,11 +2,14 @@ import sys from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite from collections.abc import Iterable, MutableSequence from types import GenericAlias -from typing import Any, ClassVar, Literal, SupportsIndex, TypeVar, overload -from typing_extensions import Self, TypeAlias, deprecated, disjoint_base +from typing import Any, ClassVar, Literal, SupportsIndex, TypeAlias, TypeVar, overload +from typing_extensions import Self, deprecated, disjoint_base _IntTypeCode: TypeAlias = Literal["b", "B", "h", "H", "i", "I", "l", "L", "q", "Q"] -_FloatTypeCode: TypeAlias = Literal["f", "d"] +if sys.version_info >= (3, 15): + _FloatTypeCode: TypeAlias = Literal["f", "d", "e", "Zf", "Zd"] +else: + _FloatTypeCode: TypeAlias = Literal["f", "d"] if sys.version_info >= (3, 13): _UnicodeTypeCode: TypeAlias = Literal["u", "w"] else: @@ -15,7 +18,10 @@ _TypeCode: TypeAlias = _IntTypeCode | _FloatTypeCode | _UnicodeTypeCode _T = TypeVar("_T", int, float, str) -typecodes: str +if sys.version_info >= (3, 15): + typecodes: tuple[str, ...] +else: + typecodes: str @disjoint_base class array(MutableSequence[_T]): @@ -23,6 +29,7 @@ class array(MutableSequence[_T]): def typecode(self) -> _TypeCode: ... @property def itemsize(self) -> int: ... + @overload def __new__( cls: type[array[int]], typecode: _IntTypeCode, initializer: bytes | bytearray | Iterable[int] = ..., / @@ -52,6 +59,7 @@ class array(MutableSequence[_T]): def __new__(cls, typecode: str, initializer: Iterable[_T], /) -> Self: ... @overload def __new__(cls, typecode: str, initializer: bytes | bytearray = ..., /) -> Self: ... + def append(self, v: _T, /) -> None: ... def buffer_info(self) -> tuple[int, int]: ... def byteswap(self) -> None: ... @@ -61,11 +69,7 @@ class array(MutableSequence[_T]): def fromfile(self, f: SupportsRead[bytes], n: int, /) -> None: ... def fromlist(self, list: list[_T], /) -> None: ... def fromunicode(self, ustr: str, /) -> None: ... - if sys.version_info >= (3, 10): - def index(self, v: _T, start: int = 0, stop: int = sys.maxsize, /) -> int: ... - else: - def index(self, v: _T, /) -> int: ... # type: ignore[override] - + def index(self, v: _T, start: int = 0, stop: int = sys.maxsize, /) -> int: ... def insert(self, i: int, v: _T, /) -> None: ... def pop(self, i: int = -1, /) -> _T: ... def remove(self, v: _T, /) -> None: ... @@ -77,14 +81,17 @@ class array(MutableSequence[_T]): __hash__: ClassVar[None] # type: ignore[assignment] def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> _T: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> array[_T]: ... + @overload # type: ignore[override] def __setitem__(self, key: SupportsIndex, value: _T, /) -> None: ... @overload def __setitem__(self, key: slice[SupportsIndex | None], value: array[_T], /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> None: ... def __add__(self, value: array[_T], /) -> array[_T]: ... def __eq__(self, value: object, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/ast.pyi b/mypy/typeshed/stdlib/ast.pyi index e66e609ee6645..a1993773ee25f 100644 --- a/mypy/typeshed/stdlib/ast.pyi +++ b/mypy/typeshed/stdlib/ast.pyi @@ -10,6 +10,7 @@ from _ast import ( ) from _typeshed import ReadableBuffer, Unused from collections.abc import Iterable, Iterator, Sequence +from types import EllipsisType from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar as _TypeVar, overload, type_check_only from typing_extensions import Self, Unpack, deprecated, disjoint_base @@ -44,16 +45,14 @@ if sys.version_info >= (3, 12): else: class AST: - if sys.version_info >= (3, 10): - __match_args__ = () + __match_args__ = () _attributes: ClassVar[tuple[str, ...]] _fields: ClassVar[tuple[str, ...]] class mod(AST): ... class Module(mod): - if sys.version_info >= (3, 10): - __match_args__ = ("body", "type_ignores") + __match_args__ = ("body", "type_ignores") body: list[stmt] type_ignores: list[TypeIgnore] if sys.version_info >= (3, 13): @@ -65,8 +64,7 @@ class Module(mod): def __replace__(self, *, body: list[stmt] = ..., type_ignores: list[TypeIgnore] = ...) -> Self: ... class Interactive(mod): - if sys.version_info >= (3, 10): - __match_args__ = ("body",) + __match_args__ = ("body",) body: list[stmt] if sys.version_info >= (3, 13): def __init__(self, body: list[stmt] = ...) -> None: ... @@ -77,8 +75,7 @@ class Interactive(mod): def __replace__(self, *, body: list[stmt] = ...) -> Self: ... class Expression(mod): - if sys.version_info >= (3, 10): - __match_args__ = ("body",) + __match_args__ = ("body",) body: expr def __init__(self, body: expr) -> None: ... @@ -86,8 +83,7 @@ class Expression(mod): def __replace__(self, *, body: expr = ...) -> Self: ... class FunctionType(mod): - if sys.version_info >= (3, 10): - __match_args__ = ("argtypes", "returns") + __match_args__ = ("argtypes", "returns") argtypes: list[expr] returns: expr if sys.version_info >= (3, 13): @@ -114,7 +110,7 @@ class stmt(AST): class FunctionDef(stmt): if sys.version_info >= (3, 12): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") - elif sys.version_info >= (3, 10): + else: __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") name: str args: arguments @@ -191,7 +187,7 @@ class FunctionDef(stmt): class AsyncFunctionDef(stmt): if sys.version_info >= (3, 12): __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params") - elif sys.version_info >= (3, 10): + else: __match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment") name: str args: arguments @@ -268,7 +264,7 @@ class AsyncFunctionDef(stmt): class ClassDef(stmt): if sys.version_info >= (3, 12): __match_args__ = ("name", "bases", "keywords", "body", "decorator_list", "type_params") - elif sys.version_info >= (3, 10): + else: __match_args__ = ("name", "bases", "keywords", "body", "decorator_list") name: str bases: list[expr] @@ -324,8 +320,7 @@ class ClassDef(stmt): ) -> Self: ... class Return(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("value",) + __match_args__ = ("value",) value: expr | None def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ... @@ -333,8 +328,7 @@ class Return(stmt): def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Delete(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("targets",) + __match_args__ = ("targets",) targets: list[expr] if sys.version_info >= (3, 13): def __init__(self, targets: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -345,8 +339,7 @@ class Delete(stmt): def __replace__(self, *, targets: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Assign(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("targets", "value", "type_comment") + __match_args__ = ("targets", "value", "type_comment") targets: list[expr] value: expr type_comment: str | None @@ -400,8 +393,7 @@ if sys.version_info >= (3, 12): ) -> Self: ... class AugAssign(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "op", "value") + __match_args__ = ("target", "op", "value") target: Name | Attribute | Subscript op: operator value: expr @@ -420,12 +412,12 @@ class AugAssign(stmt): ) -> Self: ... class AnnAssign(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "annotation", "value", "simple") + __match_args__ = ("target", "annotation", "value", "simple") target: Name | Attribute | Subscript annotation: expr value: expr | None simple: int + @overload def __init__( self, @@ -458,8 +450,7 @@ class AnnAssign(stmt): ) -> Self: ... class For(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "iter", "body", "orelse", "type_comment") + __match_args__ = ("target", "iter", "body", "orelse", "type_comment") target: expr iter: expr body: list[stmt] @@ -499,8 +490,7 @@ class For(stmt): ) -> Self: ... class AsyncFor(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "iter", "body", "orelse", "type_comment") + __match_args__ = ("target", "iter", "body", "orelse", "type_comment") target: expr iter: expr body: list[stmt] @@ -540,8 +530,7 @@ class AsyncFor(stmt): ) -> Self: ... class While(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("test", "body", "orelse") + __match_args__ = ("test", "body", "orelse") test: expr body: list[stmt] orelse: list[stmt] @@ -558,8 +547,7 @@ class While(stmt): ) -> Self: ... class If(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("test", "body", "orelse") + __match_args__ = ("test", "body", "orelse") test: expr body: list[stmt] orelse: list[stmt] @@ -576,8 +564,7 @@ class If(stmt): ) -> Self: ... class With(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("items", "body", "type_comment") + __match_args__ = ("items", "body", "type_comment") items: list[withitem] body: list[stmt] type_comment: str | None @@ -605,8 +592,7 @@ class With(stmt): ) -> Self: ... class AsyncWith(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("items", "body", "type_comment") + __match_args__ = ("items", "body", "type_comment") items: list[withitem] body: list[stmt] type_comment: str | None @@ -634,8 +620,7 @@ class AsyncWith(stmt): ) -> Self: ... class Raise(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("exc", "cause") + __match_args__ = ("exc", "cause") exc: expr | None cause: expr | None def __init__(self, exc: expr | None = None, cause: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ... @@ -644,8 +629,7 @@ class Raise(stmt): def __replace__(self, *, exc: expr | None = ..., cause: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Try(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("body", "handlers", "orelse", "finalbody") + __match_args__ = ("body", "handlers", "orelse", "finalbody") body: list[stmt] handlers: list[ExceptHandler] orelse: list[stmt] @@ -718,8 +702,7 @@ if sys.version_info >= (3, 11): ) -> Self: ... class Assert(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("test", "msg") + __match_args__ = ("test", "msg") test: expr msg: expr | None def __init__(self, test: expr, msg: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ... @@ -728,24 +711,53 @@ class Assert(stmt): def __replace__(self, *, test: expr = ..., msg: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Import(stmt): - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 15): + __match_args__ = ("names", "is_lazy") + else: __match_args__ = ("names",) names: list[alias] - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 15): + is_lazy: bool | None + if sys.version_info >= (3, 15): + def __init__(self, names: list[alias] = ..., is_lazy: bool | None = None, **kwargs: Unpack[_Attributes]) -> None: ... + + elif sys.version_info >= (3, 13): def __init__(self, names: list[alias] = ..., **kwargs: Unpack[_Attributes]) -> None: ... else: def __init__(self, names: list[alias], **kwargs: Unpack[_Attributes]) -> None: ... - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + def __replace__(self, *, names: list[alias] = ..., is_lazy: bool | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... + + elif sys.version_info >= (3, 14): def __replace__(self, *, names: list[alias] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class ImportFrom(stmt): - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 15): + __match_args__ = ("module", "names", "level", "is_lazy") + else: __match_args__ = ("module", "names", "level") module: str | None names: list[alias] level: int - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 15): + is_lazy: bool | None + if sys.version_info >= (3, 15): + @overload + def __init__( + self, module: str | None, names: list[alias], level: int, is_lazy: bool | None = None, **kwargs: Unpack[_Attributes] + ) -> None: ... + @overload + def __init__( + self, + module: str | None = None, + names: list[alias] = ..., + *, + level: int, + is_lazy: bool | None = None, + **kwargs: Unpack[_Attributes], + ) -> None: ... + elif sys.version_info >= (3, 13): @overload def __init__(self, module: str | None, names: list[alias], level: int, **kwargs: Unpack[_Attributes]) -> None: ... @overload @@ -760,14 +772,24 @@ class ImportFrom(stmt): self, module: str | None = None, *, names: list[alias], level: int, **kwargs: Unpack[_Attributes] ) -> None: ... - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + def __replace__( + self, + *, + module: str | None = ..., + names: list[alias] = ..., + level: int = ..., + is_lazy: bool | None = ..., + **kwargs: Unpack[_Attributes], + ) -> Self: ... + + elif sys.version_info >= (3, 14): def __replace__( self, *, module: str | None = ..., names: list[alias] = ..., level: int = ..., **kwargs: Unpack[_Attributes] ) -> Self: ... class Global(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("names",) + __match_args__ = ("names",) names: list[str] if sys.version_info >= (3, 13): def __init__(self, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -778,8 +800,7 @@ class Global(stmt): def __replace__(self, *, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Nonlocal(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("names",) + __match_args__ = ("names",) names: list[str] if sys.version_info >= (3, 13): def __init__(self, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -790,8 +811,7 @@ class Nonlocal(stmt): def __replace__(self, *, names: list[str] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Expr(stmt): - if sys.version_info >= (3, 10): - __match_args__ = ("value",) + __match_args__ = ("value",) value: expr def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -813,8 +833,7 @@ class expr(AST): def __replace__(self, **kwargs: Unpack[_Attributes]) -> Self: ... class BoolOp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("op", "values") + __match_args__ = ("op", "values") op: boolop values: list[expr] if sys.version_info >= (3, 13): @@ -826,8 +845,7 @@ class BoolOp(expr): def __replace__(self, *, op: boolop = ..., values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class NamedExpr(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "value") + __match_args__ = ("target", "value") target: Name value: expr def __init__(self, target: Name, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -836,8 +854,7 @@ class NamedExpr(expr): def __replace__(self, *, target: Name = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class BinOp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("left", "op", "right") + __match_args__ = ("left", "op", "right") left: expr op: operator right: expr @@ -849,8 +866,7 @@ class BinOp(expr): ) -> Self: ... class UnaryOp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("op", "operand") + __match_args__ = ("op", "operand") op: unaryop operand: expr def __init__(self, op: unaryop, operand: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -859,8 +875,7 @@ class UnaryOp(expr): def __replace__(self, *, op: unaryop = ..., operand: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Lambda(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("args", "body") + __match_args__ = ("args", "body") args: arguments body: expr def __init__(self, args: arguments, body: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -869,8 +884,7 @@ class Lambda(expr): def __replace__(self, *, args: arguments = ..., body: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class IfExp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("test", "body", "orelse") + __match_args__ = ("test", "body", "orelse") test: expr body: expr orelse: expr @@ -882,8 +896,7 @@ class IfExp(expr): ) -> Self: ... class Dict(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("keys", "values") + __match_args__ = ("keys", "values") keys: list[expr | None] values: list[expr] if sys.version_info >= (3, 13): @@ -897,8 +910,7 @@ class Dict(expr): ) -> Self: ... class Set(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elts",) + __match_args__ = ("elts",) elts: list[expr] if sys.version_info >= (3, 13): def __init__(self, elts: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -909,8 +921,7 @@ class Set(expr): def __replace__(self, *, elts: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class ListComp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elt", "generators") + __match_args__ = ("elt", "generators") elt: expr generators: list[comprehension] if sys.version_info >= (3, 13): @@ -924,8 +935,7 @@ class ListComp(expr): ) -> Self: ... class SetComp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elt", "generators") + __match_args__ = ("elt", "generators") elt: expr generators: list[comprehension] if sys.version_info >= (3, 13): @@ -939,26 +949,40 @@ class SetComp(expr): ) -> Self: ... class DictComp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("key", "value", "generators") + __match_args__ = ("key", "value", "generators") key: expr - value: expr + if sys.version_info >= (3, 15): + value: expr | None + else: + value: expr generators: list[comprehension] - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 15): + def __init__( + self, key: expr, value: expr | None = None, generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes] + ) -> None: ... + elif sys.version_info >= (3, 13): def __init__( self, key: expr, value: expr, generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes] ) -> None: ... else: def __init__(self, key: expr, value: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ... - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + def __replace__( + self, + *, + key: expr = ..., + value: expr | None = ..., + generators: list[comprehension] = ..., + **kwargs: Unpack[_Attributes], + ) -> Self: ... + elif sys.version_info >= (3, 14): def __replace__( self, *, key: expr = ..., value: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes] ) -> Self: ... class GeneratorExp(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elt", "generators") + __match_args__ = ("elt", "generators") elt: expr generators: list[comprehension] if sys.version_info >= (3, 13): @@ -972,8 +996,7 @@ class GeneratorExp(expr): ) -> Self: ... class Await(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value",) + __match_args__ = ("value",) value: expr def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -981,8 +1004,7 @@ class Await(expr): def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Yield(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value",) + __match_args__ = ("value",) value: expr | None def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ... @@ -990,8 +1012,7 @@ class Yield(expr): def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class YieldFrom(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value",) + __match_args__ = ("value",) value: expr def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @@ -999,8 +1020,7 @@ class YieldFrom(expr): def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Compare(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("left", "ops", "comparators") + __match_args__ = ("left", "ops", "comparators") left: expr ops: list[cmpop] comparators: list[expr] @@ -1017,8 +1037,7 @@ class Compare(expr): ) -> Self: ... class Call(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("func", "args", "keywords") + __match_args__ = ("func", "args", "keywords") func: expr args: list[expr] keywords: list[keyword] @@ -1035,8 +1054,7 @@ class Call(expr): ) -> Self: ... class FormattedValue(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value", "conversion", "format_spec") + __match_args__ = ("value", "conversion", "format_spec") value: expr conversion: int format_spec: expr | None @@ -1048,8 +1066,7 @@ class FormattedValue(expr): ) -> Self: ... class JoinedStr(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("values",) + __match_args__ = ("values",) values: list[expr] if sys.version_info >= (3, 13): def __init__(self, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -1090,17 +1107,10 @@ if sys.version_info >= (3, 14): **kwargs: Unpack[_Attributes], ) -> Self: ... -if sys.version_info >= (3, 10): - from types import EllipsisType - - _ConstantValue: typing_extensions.TypeAlias = str | bytes | bool | int | float | complex | None | EllipsisType -else: - # Rely on builtins.ellipsis - _ConstantValue: typing_extensions.TypeAlias = str | bytes | bool | int | float | complex | None | ellipsis # noqa: F821 +_ConstantValue: typing_extensions.TypeAlias = str | bytes | bool | int | float | complex | None | EllipsisType class Constant(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value", "kind") + __match_args__ = ("value", "kind") value: _ConstantValue kind: str | None if sys.version_info < (3, 14): @@ -1111,6 +1121,7 @@ class Constant(expr): @n.setter @deprecated("Removed in Python 3.14. Use `value` instead.") def n(self, value: _ConstantValue) -> None: ... + @property @deprecated("Removed in Python 3.14. Use `value` instead.") def s(self) -> _ConstantValue: ... @@ -1124,8 +1135,7 @@ class Constant(expr): def __replace__(self, *, value: _ConstantValue = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Attribute(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value", "attr", "ctx") + __match_args__ = ("value", "attr", "ctx") value: expr attr: str ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` @@ -1137,8 +1147,7 @@ class Attribute(expr): ) -> Self: ... class Subscript(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value", "slice", "ctx") + __match_args__ = ("value", "slice", "ctx") value: expr slice: expr ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` @@ -1150,8 +1159,7 @@ class Subscript(expr): ) -> Self: ... class Starred(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("value", "ctx") + __match_args__ = ("value", "ctx") value: expr ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` def __init__(self, value: expr, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -1160,8 +1168,7 @@ class Starred(expr): def __replace__(self, *, value: expr = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Name(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("id", "ctx") + __match_args__ = ("id", "ctx") id: str ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` def __init__(self, id: str, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ... @@ -1170,8 +1177,7 @@ class Name(expr): def __replace__(self, *, id: str = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class List(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elts", "ctx") + __match_args__ = ("elts", "ctx") elts: list[expr] ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` if sys.version_info >= (3, 13): @@ -1183,8 +1189,7 @@ class List(expr): def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class Tuple(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("elts", "ctx") + __match_args__ = ("elts", "ctx") elts: list[expr] ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__` dims: list[expr] @@ -1200,8 +1205,7 @@ class Tuple(expr): class slice(AST): ... class Slice(expr): - if sys.version_info >= (3, 10): - __match_args__ = ("lower", "upper", "step") + __match_args__ = ("lower", "upper", "step") lower: expr | None upper: expr | None step: expr | None @@ -1274,8 +1278,7 @@ class In(cmpop): ... class NotIn(cmpop): ... class comprehension(AST): - if sys.version_info >= (3, 10): - __match_args__ = ("target", "iter", "ifs", "is_async") + __match_args__ = ("target", "iter", "ifs", "is_async") target: expr iter: expr ifs: list[expr] @@ -1304,8 +1307,7 @@ class excepthandler(AST): ) -> Self: ... class ExceptHandler(excepthandler): - if sys.version_info >= (3, 10): - __match_args__ = ("type", "name", "body") + __match_args__ = ("type", "name", "body") type: expr | None name: str | None body: list[stmt] @@ -1327,8 +1329,7 @@ class ExceptHandler(excepthandler): ) -> Self: ... class arguments(AST): - if sys.version_info >= (3, 10): - __match_args__ = ("posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults") + __match_args__ = ("posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults") posonlyargs: list[arg] args: list[arg] vararg: arg | None @@ -1398,12 +1399,11 @@ class arguments(AST): ) -> Self: ... class arg(AST): + __match_args__ = ("arg", "annotation", "type_comment") lineno: int col_offset: int end_lineno: int | None end_col_offset: int | None - if sys.version_info >= (3, 10): - __match_args__ = ("arg", "annotation", "type_comment") arg: str annotation: expr | None type_comment: str | None @@ -1417,14 +1417,14 @@ class arg(AST): ) -> Self: ... class keyword(AST): + __match_args__ = ("arg", "value") lineno: int col_offset: int end_lineno: int | None end_col_offset: int | None - if sys.version_info >= (3, 10): - __match_args__ = ("arg", "value") arg: str | None value: expr + @overload def __init__(self, arg: str | None, value: expr, **kwargs: Unpack[_Attributes]) -> None: ... @overload @@ -1434,26 +1434,20 @@ class keyword(AST): def __replace__(self, *, arg: str | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class alias(AST): + __match_args__ = ("name", "asname") name: str asname: str | None - if sys.version_info >= (3, 10): - lineno: int - col_offset: int - end_lineno: int | None - end_col_offset: int | None - if sys.version_info >= (3, 10): - __match_args__ = ("name", "asname") - if sys.version_info >= (3, 10): - def __init__(self, name: str, asname: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... - else: - def __init__(self, name: str, asname: str | None = None) -> None: ... + lineno: int + col_offset: int + end_lineno: int | None + end_col_offset: int | None + def __init__(self, name: str, asname: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ... if sys.version_info >= (3, 14): def __replace__(self, *, name: str = ..., asname: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ... class withitem(AST): - if sys.version_info >= (3, 10): - __match_args__ = ("context_expr", "optional_vars") + __match_args__ = ("context_expr", "optional_vars") context_expr: expr optional_vars: expr | None def __init__(self, context_expr: expr, optional_vars: expr | None = None) -> None: ... @@ -1461,177 +1455,173 @@ class withitem(AST): if sys.version_info >= (3, 14): def __replace__(self, *, context_expr: expr = ..., optional_vars: expr | None = ...) -> Self: ... -if sys.version_info >= (3, 10): - class pattern(AST): - lineno: int - col_offset: int - end_lineno: int - end_col_offset: int - def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ... +class pattern(AST): + lineno: int + col_offset: int + end_lineno: int + end_col_offset: int + def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__( - self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int = ..., end_col_offset: int = ... - ) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__( + self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int = ..., end_col_offset: int = ... + ) -> Self: ... - class match_case(AST): - __match_args__ = ("pattern", "guard", "body") - pattern: ast.pattern - guard: expr | None - body: list[stmt] - if sys.version_info >= (3, 13): - def __init__(self, pattern: ast.pattern, guard: expr | None = None, body: list[stmt] = ...) -> None: ... - elif sys.version_info >= (3, 10): - @overload - def __init__(self, pattern: ast.pattern, guard: expr | None, body: list[stmt]) -> None: ... - @overload - def __init__(self, pattern: ast.pattern, guard: expr | None = None, *, body: list[stmt]) -> None: ... +class match_case(AST): + __match_args__ = ("pattern", "guard", "body") + pattern: ast.pattern + guard: expr | None + body: list[stmt] + if sys.version_info >= (3, 13): + def __init__(self, pattern: ast.pattern, guard: expr | None = None, body: list[stmt] = ...) -> None: ... + else: + @overload + def __init__(self, pattern: ast.pattern, guard: expr | None, body: list[stmt]) -> None: ... + @overload + def __init__(self, pattern: ast.pattern, guard: expr | None = None, *, body: list[stmt]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, pattern: ast.pattern = ..., guard: expr | None = ..., body: list[stmt] = ...) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, pattern: ast.pattern = ..., guard: expr | None = ..., body: list[stmt] = ...) -> Self: ... - class Match(stmt): - __match_args__ = ("subject", "cases") - subject: expr - cases: list[match_case] - if sys.version_info >= (3, 13): - def __init__(self, subject: expr, cases: list[match_case] = ..., **kwargs: Unpack[_Attributes]) -> None: ... - else: - def __init__(self, subject: expr, cases: list[match_case], **kwargs: Unpack[_Attributes]) -> None: ... +class Match(stmt): + __match_args__ = ("subject", "cases") + subject: expr + cases: list[match_case] + if sys.version_info >= (3, 13): + def __init__(self, subject: expr, cases: list[match_case] = ..., **kwargs: Unpack[_Attributes]) -> None: ... + else: + def __init__(self, subject: expr, cases: list[match_case], **kwargs: Unpack[_Attributes]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__( - self, *, subject: expr = ..., cases: list[match_case] = ..., **kwargs: Unpack[_Attributes] - ) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, subject: expr = ..., cases: list[match_case] = ..., **kwargs: Unpack[_Attributes]) -> Self: ... - class MatchValue(pattern): - __match_args__ = ("value",) - value: expr - def __init__(self, value: expr, **kwargs: Unpack[_Attributes[int]]) -> None: ... +class MatchValue(pattern): + __match_args__ = ("value",) + value: expr + def __init__(self, value: expr, **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... - class MatchSingleton(pattern): - __match_args__ = ("value",) - value: bool | None - def __init__(self, value: bool | None, **kwargs: Unpack[_Attributes[int]]) -> None: ... +class MatchSingleton(pattern): + __match_args__ = ("value",) + value: bool | None + def __init__(self, value: bool | None, **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, value: bool | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, value: bool | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... - class MatchSequence(pattern): - __match_args__ = ("patterns",) - patterns: list[pattern] - if sys.version_info >= (3, 13): - def __init__(self, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> None: ... - else: - def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ... +class MatchSequence(pattern): + __match_args__ = ("patterns",) + patterns: list[pattern] + if sys.version_info >= (3, 13): + def __init__(self, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> None: ... + else: + def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... - class MatchMapping(pattern): - __match_args__ = ("keys", "patterns", "rest") - keys: list[expr] - patterns: list[pattern] - rest: str | None - if sys.version_info >= (3, 13): - def __init__( - self, - keys: list[expr] = ..., - patterns: list[pattern] = ..., - rest: str | None = None, - **kwargs: Unpack[_Attributes[int]], - ) -> None: ... - else: - def __init__( - self, keys: list[expr], patterns: list[pattern], rest: str | None = None, **kwargs: Unpack[_Attributes[int]] - ) -> None: ... +class MatchMapping(pattern): + __match_args__ = ("keys", "patterns", "rest") + keys: list[expr] + patterns: list[pattern] + rest: str | None + if sys.version_info >= (3, 13): + def __init__( + self, + keys: list[expr] = ..., + patterns: list[pattern] = ..., + rest: str | None = None, + **kwargs: Unpack[_Attributes[int]], + ) -> None: ... + else: + def __init__( + self, keys: list[expr], patterns: list[pattern], rest: str | None = None, **kwargs: Unpack[_Attributes[int]] + ) -> None: ... - if sys.version_info >= (3, 14): - def __replace__( - self, - *, - keys: list[expr] = ..., - patterns: list[pattern] = ..., - rest: str | None = ..., - **kwargs: Unpack[_Attributes[int]], - ) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__( + self, + *, + keys: list[expr] = ..., + patterns: list[pattern] = ..., + rest: str | None = ..., + **kwargs: Unpack[_Attributes[int]], + ) -> Self: ... - class MatchClass(pattern): - __match_args__ = ("cls", "patterns", "kwd_attrs", "kwd_patterns") - cls: expr - patterns: list[pattern] - kwd_attrs: list[str] - kwd_patterns: list[pattern] - if sys.version_info >= (3, 13): - def __init__( - self, - cls: expr, - patterns: list[pattern] = ..., - kwd_attrs: list[str] = ..., - kwd_patterns: list[pattern] = ..., - **kwargs: Unpack[_Attributes[int]], - ) -> None: ... - else: - def __init__( - self, - cls: expr, - patterns: list[pattern], - kwd_attrs: list[str], - kwd_patterns: list[pattern], - **kwargs: Unpack[_Attributes[int]], - ) -> None: ... +class MatchClass(pattern): + __match_args__ = ("cls", "patterns", "kwd_attrs", "kwd_patterns") + cls: expr + patterns: list[pattern] + kwd_attrs: list[str] + kwd_patterns: list[pattern] + if sys.version_info >= (3, 13): + def __init__( + self, + cls: expr, + patterns: list[pattern] = ..., + kwd_attrs: list[str] = ..., + kwd_patterns: list[pattern] = ..., + **kwargs: Unpack[_Attributes[int]], + ) -> None: ... + else: + def __init__( + self, + cls: expr, + patterns: list[pattern], + kwd_attrs: list[str], + kwd_patterns: list[pattern], + **kwargs: Unpack[_Attributes[int]], + ) -> None: ... - if sys.version_info >= (3, 14): - def __replace__( - self, - *, - cls: expr = ..., - patterns: list[pattern] = ..., - kwd_attrs: list[str] = ..., - kwd_patterns: list[pattern] = ..., - **kwargs: Unpack[_Attributes[int]], - ) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__( + self, + *, + cls: expr = ..., + patterns: list[pattern] = ..., + kwd_attrs: list[str] = ..., + kwd_patterns: list[pattern] = ..., + **kwargs: Unpack[_Attributes[int]], + ) -> Self: ... - class MatchStar(pattern): - __match_args__ = ("name",) - name: str | None - def __init__(self, name: str | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... +class MatchStar(pattern): + __match_args__ = ("name",) + name: str | None + def __init__(self, name: str | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, name: str | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, name: str | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... - class MatchAs(pattern): - __match_args__ = ("pattern", "name") - pattern: ast.pattern | None - name: str | None - def __init__( - self, pattern: ast.pattern | None = None, name: str | None = None, **kwargs: Unpack[_Attributes[int]] - ) -> None: ... +class MatchAs(pattern): + __match_args__ = ("pattern", "name") + pattern: ast.pattern | None + name: str | None + def __init__( + self, pattern: ast.pattern | None = None, name: str | None = None, **kwargs: Unpack[_Attributes[int]] + ) -> None: ... - if sys.version_info >= (3, 14): - def __replace__( - self, *, pattern: ast.pattern | None = ..., name: str | None = ..., **kwargs: Unpack[_Attributes[int]] - ) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__( + self, *, pattern: ast.pattern | None = ..., name: str | None = ..., **kwargs: Unpack[_Attributes[int]] + ) -> Self: ... - class MatchOr(pattern): - __match_args__ = ("patterns",) - patterns: list[pattern] - if sys.version_info >= (3, 13): - def __init__(self, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> None: ... - else: - def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ... +class MatchOr(pattern): + __match_args__ = ("patterns",) + patterns: list[pattern] + if sys.version_info >= (3, 13): + def __init__(self, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> None: ... + else: + def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ... - if sys.version_info >= (3, 14): - def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... + if sys.version_info >= (3, 14): + def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ... class type_ignore(AST): ... class TypeIgnore(type_ignore): - if sys.version_info >= (3, 10): - __match_args__ = ("lineno", "tag") + __match_args__ = ("lineno", "tag") lineno: int tag: str def __init__(self, lineno: int, tag: str) -> None: ... @@ -1743,7 +1733,104 @@ if sys.version_info < (3, 14): _T = _TypeVar("_T", bound=AST) -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 15): + @overload + def parse( + source: _T, + filename: str | bytes | os.PathLike[Any] = "", + mode: Literal["exec", "eval", "func_type", "single"] = "exec", + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> _T: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | bytes | os.PathLike[Any] = "", + mode: Literal["exec"] = "exec", + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> Module: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | bytes | os.PathLike[Any], + mode: Literal["eval"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> Expression: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | bytes | os.PathLike[Any], + mode: Literal["func_type"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> FunctionType: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | bytes | os.PathLike[Any], + mode: Literal["single"], + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> Interactive: ... + @overload + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["eval"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> Expression: ... + @overload + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["func_type"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> FunctionType: ... + @overload + def parse( + source: str | ReadableBuffer, + *, + mode: Literal["single"], + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> Interactive: ... + @overload + def parse( + source: str | ReadableBuffer, + filename: str | bytes | os.PathLike[Any] = "", + mode: str = "exec", + *, + type_comments: bool = False, + feature_version: None | int | tuple[int, int] = None, + optimize: Literal[-1, 0, 1, 2] = -1, + module: str | None = None, + ) -> mod: ... +elif sys.version_info >= (3, 13): @overload def parse( source: _T, @@ -1831,7 +1918,6 @@ if sys.version_info >= (3, 13): feature_version: None | int | tuple[int, int] = None, optimize: Literal[-1, 0, 1, 2] = -1, ) -> mod: ... - else: @overload def parse( @@ -1914,7 +2000,18 @@ else: def literal_eval(node_or_string: str | AST) -> Any: ... -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 15): + def dump( + node: AST, + annotate_fields: bool = True, + include_attributes: bool = False, + *, + indent: int | str | None = None, + show_empty: bool = False, + color: bool = False, + ) -> str: ... + +elif sys.version_info >= (3, 13): def dump( node: AST, annotate_fields: bool = True, @@ -2043,17 +2140,16 @@ class NodeVisitor: def visit_keyword(self, node: keyword) -> Any: ... def visit_alias(self, node: alias) -> Any: ... def visit_withitem(self, node: withitem) -> Any: ... - if sys.version_info >= (3, 10): - def visit_Match(self, node: Match) -> Any: ... - def visit_match_case(self, node: match_case) -> Any: ... - def visit_MatchValue(self, node: MatchValue) -> Any: ... - def visit_MatchSequence(self, node: MatchSequence) -> Any: ... - def visit_MatchSingleton(self, node: MatchSingleton) -> Any: ... - def visit_MatchStar(self, node: MatchStar) -> Any: ... - def visit_MatchMapping(self, node: MatchMapping) -> Any: ... - def visit_MatchClass(self, node: MatchClass) -> Any: ... - def visit_MatchAs(self, node: MatchAs) -> Any: ... - def visit_MatchOr(self, node: MatchOr) -> Any: ... + def visit_Match(self, node: Match) -> Any: ... + def visit_match_case(self, node: match_case) -> Any: ... + def visit_MatchValue(self, node: MatchValue) -> Any: ... + def visit_MatchSequence(self, node: MatchSequence) -> Any: ... + def visit_MatchSingleton(self, node: MatchSingleton) -> Any: ... + def visit_MatchStar(self, node: MatchStar) -> Any: ... + def visit_MatchMapping(self, node: MatchMapping) -> Any: ... + def visit_MatchClass(self, node: MatchClass) -> Any: ... + def visit_MatchAs(self, node: MatchAs) -> Any: ... + def visit_MatchOr(self, node: MatchOr) -> Any: ... if sys.version_info >= (3, 11): def visit_TryStar(self, node: TryStar) -> Any: ... @@ -2064,6 +2160,10 @@ class NodeVisitor: def visit_TypeVarTuple(self, node: TypeVarTuple) -> Any: ... def visit_TypeAlias(self, node: TypeAlias) -> Any: ... + if sys.version_info >= (3, 14): + def visit_TemplateStr(self, node: TemplateStr) -> Any: ... + def visit_Interpolation(self, node: Interpolation) -> Any: ... + # visit methods for deprecated nodes def visit_ExtSlice(self, node: ExtSlice) -> Any: ... def visit_Index(self, node: Index) -> Any: ... diff --git a/mypy/typeshed/stdlib/asyncio/__init__.pyi b/mypy/typeshed/stdlib/asyncio/__init__.pyi index 23cf57aaac335..5748c85af4c6c 100644 --- a/mypy/typeshed/stdlib/asyncio/__init__.pyi +++ b/mypy/typeshed/stdlib/asyncio/__init__.pyi @@ -2,8 +2,7 @@ # Can't NOQA on a specific line: https://github.com/plinss/flake8-noqa/issues/22 import sys from collections.abc import Awaitable, Coroutine, Generator -from typing import Any, TypeVar -from typing_extensions import TypeAlias +from typing import Any, TypeAlias, TypeVar # As at runtime, this depends on all submodules defining __all__ accurately. from .base_events import * @@ -33,6 +32,24 @@ if sys.platform == "win32": else: from .unix_events import * +if sys.version_info >= (3, 14): + from .events import _AbstractEventLoopPolicy + + AbstractEventLoopPolicy = _AbstractEventLoopPolicy + +if sys.platform == "win32": + if sys.version_info >= (3, 14): + from .windows_events import _DefaultEventLoopPolicy, _WindowsProactorEventLoopPolicy, _WindowsSelectorEventLoopPolicy + + DefaultEventLoopPolicy = _DefaultEventLoopPolicy + WindowsProactorEventLoopPolicy = _WindowsProactorEventLoopPolicy + WindowsSelectorEventLoopPolicy = _WindowsSelectorEventLoopPolicy +else: + if sys.version_info >= (3, 14): + from .unix_events import _DefaultEventLoopPolicy + + DefaultEventLoopPolicy = _DefaultEventLoopPolicy + if sys.platform == "win32": if sys.version_info >= (3, 14): diff --git a/mypy/typeshed/stdlib/asyncio/base_events.pyi b/mypy/typeshed/stdlib/asyncio/base_events.pyi index 0d8ac2d474914..9a3367b6aa3b2 100644 --- a/mypy/typeshed/stdlib/asyncio/base_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_events.pyi @@ -11,8 +11,8 @@ from collections.abc import Callable, Iterable, Sequence from concurrent.futures import Executor, ThreadPoolExecutor from contextvars import Context from socket import AddressFamily, AddressInfo, SocketKind, _Address, _RetAddress, socket -from typing import IO, Any, Literal, TypeVar, overload -from typing_extensions import TypeAlias, TypeVarTuple, Unpack +from typing import IO, Any, Literal, TypeAlias, TypeVar, overload +from typing_extensions import TypeVarTuple, Unpack # Keep asyncio.__all__ updated with any changes to __all__ here __all__ = ("BaseEventLoop", "Server") @@ -115,8 +115,9 @@ class BaseEventLoop(AbstractEventLoop): type: int = 0, proto: int = 0, flags: int = 0, - ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]: ... + ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]]]: ... async def getnameinfo(self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int = 0) -> tuple[str, str]: ... + if sys.version_info >= (3, 12): @overload async def create_connection( diff --git a/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi b/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi index a5fe24e8768b7..36f0f6099cbc6 100644 --- a/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi +++ b/mypy/typeshed/stdlib/asyncio/base_subprocess.pyi @@ -1,8 +1,7 @@ import subprocess from collections import deque from collections.abc import Callable, Sequence -from typing import IO, Any -from typing_extensions import TypeAlias +from typing import IO, Any, TypeAlias from . import events, futures, protocols, transports diff --git a/mypy/typeshed/stdlib/asyncio/coroutines.pyi b/mypy/typeshed/stdlib/asyncio/coroutines.pyi index 777961d804412..7599c692949bd 100644 --- a/mypy/typeshed/stdlib/asyncio/coroutines.pyi +++ b/mypy/typeshed/stdlib/asyncio/coroutines.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Awaitable, Callable, Coroutine -from typing import Any, TypeVar, overload -from typing_extensions import ParamSpec, TypeGuard, TypeIs, deprecated +from typing import Any, ParamSpec, TypeGuard, TypeVar, overload +from typing_extensions import TypeIs, deprecated # Keep asyncio.__all__ updated with any changes to __all__ here if sys.version_info >= (3, 11): @@ -32,11 +32,9 @@ if sys.version_info >= (3, 11): @overload @deprecated("Deprecated since Python 3.14. Use `inspect.iscoroutinefunction()` instead.") def iscoroutinefunction(func: object) -> TypeGuard[Callable[..., Coroutine[Any, Any, Any]]]: ... - else: # Sometimes needed in Python < 3.11 due to the fact that it supports @coroutine # which was removed in 3.11 which the inspect version doesn't support. - @overload def iscoroutinefunction(func: Callable[..., Coroutine[Any, Any, Any]]) -> bool: ... @overload diff --git a/mypy/typeshed/stdlib/asyncio/events.pyi b/mypy/typeshed/stdlib/asyncio/events.pyi index 4f2f45355f5ba..5e561b09cd460 100644 --- a/mypy/typeshed/stdlib/asyncio/events.pyi +++ b/mypy/typeshed/stdlib/asyncio/events.pyi @@ -12,8 +12,8 @@ from collections.abc import Callable, Sequence from concurrent.futures import Executor from contextvars import Context from socket import AddressFamily, AddressInfo, SocketKind, _Address, _RetAddress, socket -from typing import IO, Any, Literal, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, TypeVarTuple, Unpack, deprecated +from typing import IO, Any, Literal, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, TypeVarTuple, Unpack, deprecated from . import _AwaitableLike, _CoroutineLike from .base_events import Server @@ -205,9 +205,10 @@ class AbstractEventLoop: type: int = 0, proto: int = 0, flags: int = 0, - ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]: ... + ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]]]: ... @abstractmethod async def getnameinfo(self, sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int = 0) -> tuple[str, str]: ... + if sys.version_info >= (3, 11): @overload @abstractmethod @@ -467,7 +468,7 @@ class AbstractEventLoop: ssl_handshake_timeout: float | None = None, ssl_shutdown_timeout: float | None = None, ) -> tuple[Transport, _ProtocolT]: ... - elif sys.version_info >= (3, 10): + else: async def connect_accepted_socket( self, protocol_factory: Callable[[], _ProtocolT], @@ -631,18 +632,12 @@ else: @abstractmethod def new_event_loop(self) -> AbstractEventLoop: ... # Child processes handling (Unix only). - if sys.version_info >= (3, 12): - @abstractmethod - @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") - def get_child_watcher(self) -> AbstractChildWatcher: ... - @abstractmethod - @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") - def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... - else: - @abstractmethod - def get_child_watcher(self) -> AbstractChildWatcher: ... - @abstractmethod - def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... + @abstractmethod + @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") + def get_child_watcher(self) -> AbstractChildWatcher: ... + @abstractmethod + @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") + def set_child_watcher(self, watcher: AbstractChildWatcher) -> None: ... AbstractEventLoopPolicy = _AbstractEventLoopPolicy @@ -661,25 +656,16 @@ else: if sys.version_info >= (3, 14): def _get_event_loop_policy() -> _AbstractEventLoopPolicy: ... def _set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... - @deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") - def get_event_loop_policy() -> _AbstractEventLoopPolicy: ... - @deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") - def set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... - -else: - def get_event_loop_policy() -> _AbstractEventLoopPolicy: ... - def set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... +@deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") +def get_event_loop_policy() -> _AbstractEventLoopPolicy: ... +@deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") +def set_event_loop_policy(policy: _AbstractEventLoopPolicy | None) -> None: ... def set_event_loop(loop: AbstractEventLoop | None) -> None: ... def new_event_loop() -> AbstractEventLoop: ... if sys.version_info < (3, 14): - if sys.version_info >= (3, 12): - @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") - def get_child_watcher() -> AbstractChildWatcher: ... - @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") - def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... - - else: - def get_child_watcher() -> AbstractChildWatcher: ... - def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") + def get_child_watcher() -> AbstractChildWatcher: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") + def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/format_helpers.pyi b/mypy/typeshed/stdlib/asyncio/format_helpers.pyi index 597eb9e56e1a1..ff830a3b73d45 100644 --- a/mypy/typeshed/stdlib/asyncio/format_helpers.pyi +++ b/mypy/typeshed/stdlib/asyncio/format_helpers.pyi @@ -3,8 +3,7 @@ import sys import traceback from collections.abc import Iterable from types import FrameType, FunctionType -from typing import Any, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Any, TypeAlias, overload, type_check_only @type_check_only class _HasWrapper: diff --git a/mypy/typeshed/stdlib/asyncio/graph.pyi b/mypy/typeshed/stdlib/asyncio/graph.pyi index 18a8a6457d757..2f89de71b16b6 100644 --- a/mypy/typeshed/stdlib/asyncio/graph.pyi +++ b/mypy/typeshed/stdlib/asyncio/graph.pyi @@ -22,6 +22,7 @@ if sys.version_info >= (3, 14): def capture_call_graph(future: None = None, /, *, depth: int = 1, limit: int | None = None) -> FutureCallGraph | None: ... @overload def capture_call_graph(future: Future[Any], /, *, depth: int = 1, limit: int | None = None) -> FutureCallGraph | None: ... + def format_call_graph(future: Future[Any] | None = None, /, *, depth: int = 1, limit: int | None = None) -> str: ... def print_call_graph( future: Future[Any] | None = None, /, *, file: SupportsWrite[str] | None = None, depth: int = 1, limit: int | None = None diff --git a/mypy/typeshed/stdlib/asyncio/locks.pyi b/mypy/typeshed/stdlib/asyncio/locks.pyi index 17390b0c5a0ee..4420d02fcd269 100644 --- a/mypy/typeshed/stdlib/asyncio/locks.pyi +++ b/mypy/typeshed/stdlib/asyncio/locks.pyi @@ -7,13 +7,8 @@ from types import TracebackType from typing import Any, Literal, TypeVar from typing_extensions import Self -from .events import AbstractEventLoop from .futures import Future - -if sys.version_info >= (3, 10): - from .mixins import _LoopBoundMixin -else: - _LoopBoundMixin = object +from .mixins import _LoopBoundMixin # Keep asyncio.__all__ updated with any changes to __all__ here if sys.version_info >= (3, 11): @@ -31,22 +26,14 @@ class _ContextManagerMixin: class Lock(_ContextManagerMixin, _LoopBoundMixin): _waiters: deque[Future[Any]] | None - if sys.version_info >= (3, 10): - def __init__(self) -> None: ... - else: - def __init__(self, *, loop: AbstractEventLoop | None = None) -> None: ... - + def __init__(self) -> None: ... def locked(self) -> bool: ... async def acquire(self) -> Literal[True]: ... def release(self) -> None: ... class Event(_LoopBoundMixin): _waiters: deque[Future[Any]] - if sys.version_info >= (3, 10): - def __init__(self) -> None: ... - else: - def __init__(self, *, loop: AbstractEventLoop | None = None) -> None: ... - + def __init__(self) -> None: ... def is_set(self) -> bool: ... def set(self) -> None: ... def clear(self) -> None: ... @@ -54,11 +41,7 @@ class Event(_LoopBoundMixin): class Condition(_ContextManagerMixin, _LoopBoundMixin): _waiters: deque[Future[Any]] - if sys.version_info >= (3, 10): - def __init__(self, lock: Lock | None = None) -> None: ... - else: - def __init__(self, lock: Lock | None = None, *, loop: AbstractEventLoop | None = None) -> None: ... - + def __init__(self, lock: Lock | None = None) -> None: ... def locked(self) -> bool: ... async def acquire(self) -> Literal[True]: ... def release(self) -> None: ... @@ -70,11 +53,7 @@ class Condition(_ContextManagerMixin, _LoopBoundMixin): class Semaphore(_ContextManagerMixin, _LoopBoundMixin): _value: int _waiters: deque[Future[Any]] | None - if sys.version_info >= (3, 10): - def __init__(self, value: int = 1) -> None: ... - else: - def __init__(self, value: int = 1, *, loop: AbstractEventLoop | None = None) -> None: ... - + def __init__(self, value: int = 1) -> None: ... def locked(self) -> bool: ... async def acquire(self) -> Literal[True]: ... def release(self) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/proactor_events.pyi b/mypy/typeshed/stdlib/asyncio/proactor_events.pyi index 909d671df289d..09c096d40f04b 100644 --- a/mypy/typeshed/stdlib/asyncio/proactor_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/proactor_events.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Mapping from socket import socket from typing import Any, ClassVar, Literal @@ -20,27 +19,16 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin, transports.BaseTr def __del__(self) -> None: ... class _ProactorReadPipeTransport(_ProactorBasePipeTransport, transports.ReadTransport): - if sys.version_info >= (3, 10): - def __init__( - self, - loop: events.AbstractEventLoop, - sock: socket, - protocol: streams.StreamReaderProtocol, - waiter: futures.Future[Any] | None = None, - extra: Mapping[Any, Any] | None = None, - server: events.AbstractServer | None = None, - buffer_size: int = 65536, - ) -> None: ... - else: - def __init__( - self, - loop: events.AbstractEventLoop, - sock: socket, - protocol: streams.StreamReaderProtocol, - waiter: futures.Future[Any] | None = None, - extra: Mapping[Any, Any] | None = None, - server: events.AbstractServer | None = None, - ) -> None: ... + def __init__( + self, + loop: events.AbstractEventLoop, + sock: socket, + protocol: streams.StreamReaderProtocol, + waiter: futures.Future[Any] | None = None, + extra: Mapping[Any, Any] | None = None, + server: events.AbstractServer | None = None, + buffer_size: int = 65536, + ) -> None: ... class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport, transports.WriteTransport): ... class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport): ... diff --git a/mypy/typeshed/stdlib/asyncio/queues.pyi b/mypy/typeshed/stdlib/asyncio/queues.pyi index 2fa2226d0e6ae..de7c4879d348e 100644 --- a/mypy/typeshed/stdlib/asyncio/queues.pyi +++ b/mypy/typeshed/stdlib/asyncio/queues.pyi @@ -1,13 +1,9 @@ import sys from _typeshed import SupportsRichComparisonT -from asyncio.events import AbstractEventLoop from types import GenericAlias from typing import Any, Generic, TypeVar -if sys.version_info >= (3, 10): - from .mixins import _LoopBoundMixin -else: - _LoopBoundMixin = object +from .mixins import _LoopBoundMixin class QueueEmpty(Exception): ... class QueueFull(Exception): ... @@ -24,14 +20,8 @@ _T = TypeVar("_T") if sys.version_info >= (3, 13): class QueueShutDown(Exception): ... -# If Generic[_T] is last and _LoopBoundMixin is object, pyright is unhappy. -# We can remove the noqa pragma when dropping 3.9 support. -class Queue(Generic[_T], _LoopBoundMixin): # noqa: Y059 - if sys.version_info >= (3, 10): - def __init__(self, maxsize: int = 0) -> None: ... - else: - def __init__(self, maxsize: int = 0, *, loop: AbstractEventLoop | None = None) -> None: ... - +class Queue(_LoopBoundMixin, Generic[_T]): + def __init__(self, maxsize: int = 0) -> None: ... def _init(self, maxsize: int) -> None: ... def _get(self) -> _T: ... def _put(self, item: _T) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/runners.pyi b/mypy/typeshed/stdlib/asyncio/runners.pyi index a100c9bcec6d6..3a1e33aac689e 100644 --- a/mypy/typeshed/stdlib/asyncio/runners.pyi +++ b/mypy/typeshed/stdlib/asyncio/runners.pyi @@ -27,7 +27,12 @@ if sys.version_info >= (3, 11): else: def run(self, coro: Coroutine[Any, Any, _T], *, context: Context | None = None) -> _T: ... -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 14): + def run( + main: Awaitable[_T], *, debug: bool | None = None, loop_factory: Callable[[], AbstractEventLoop] | None = None + ) -> _T: ... + +elif sys.version_info >= (3, 12): def run( main: Coroutine[Any, Any, _T], *, debug: bool | None = None, loop_factory: Callable[[], AbstractEventLoop] | None = None ) -> _T: ... diff --git a/mypy/typeshed/stdlib/asyncio/sslproto.pyi b/mypy/typeshed/stdlib/asyncio/sslproto.pyi index ab102f124c2e8..72b4ea449e867 100644 --- a/mypy/typeshed/stdlib/asyncio/sslproto.pyi +++ b/mypy/typeshed/stdlib/asyncio/sslproto.pyi @@ -3,8 +3,7 @@ import sys from collections import deque from collections.abc import Callable from enum import Enum -from typing import Any, ClassVar, Final, Literal -from typing_extensions import TypeAlias +from typing import Any, ClassVar, Final, Literal, TypeAlias from . import constants, events, futures, protocols, transports diff --git a/mypy/typeshed/stdlib/asyncio/streams.pyi b/mypy/typeshed/stdlib/asyncio/streams.pyi index 33cffb11ed780..9e76c69d8732e 100644 --- a/mypy/typeshed/stdlib/asyncio/streams.pyi +++ b/mypy/typeshed/stdlib/asyncio/streams.pyi @@ -3,8 +3,8 @@ import sys from _typeshed import ReadableBuffer, StrPath from collections.abc import Awaitable, Callable, Iterable, Sequence, Sized from types import ModuleType -from typing import Any, Protocol, SupportsIndex, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, Protocol, SupportsIndex, TypeAlias, type_check_only +from typing_extensions import Self from . import events, protocols, transports from .base_events import Server @@ -28,67 +28,32 @@ _ClientConnectedCallback: TypeAlias = Callable[[StreamReader, StreamWriter], Awa @type_check_only class _ReaduntilBuffer(ReadableBuffer, Sized, Protocol): ... -if sys.version_info >= (3, 10): - async def open_connection( - host: str | None = None, - port: int | str | None = None, - *, - limit: int = 65536, - ssl_handshake_timeout: float | None = None, - **kwds: Any, - ) -> tuple[StreamReader, StreamWriter]: ... - async def start_server( - client_connected_cb: _ClientConnectedCallback, - host: str | Sequence[str] | None = None, - port: int | str | None = None, - *, - limit: int = 65536, - ssl_handshake_timeout: float | None = None, - **kwds: Any, - ) -> Server: ... +async def open_connection( + host: str | None = None, + port: int | str | None = None, + *, + limit: int = 65536, + ssl_handshake_timeout: float | None = None, + **kwds: Any, +) -> tuple[StreamReader, StreamWriter]: ... +async def start_server( + client_connected_cb: _ClientConnectedCallback, + host: str | Sequence[str] | None = None, + port: int | str | None = None, + *, + limit: int = 65536, + ssl_handshake_timeout: float | None = None, + **kwds: Any, +) -> Server: ... -else: - async def open_connection( - host: str | None = None, - port: int | str | None = None, - *, - loop: events.AbstractEventLoop | None = None, - limit: int = 65536, - ssl_handshake_timeout: float | None = None, - **kwds: Any, +if sys.platform != "win32": + async def open_unix_connection( + path: StrPath | None = None, *, limit: int = 65536, **kwds: Any ) -> tuple[StreamReader, StreamWriter]: ... - async def start_server( - client_connected_cb: _ClientConnectedCallback, - host: str | None = None, - port: int | str | None = None, - *, - loop: events.AbstractEventLoop | None = None, - limit: int = 65536, - ssl_handshake_timeout: float | None = None, - **kwds: Any, + async def start_unix_server( + client_connected_cb: _ClientConnectedCallback, path: StrPath | None = None, *, limit: int = 65536, **kwds: Any ) -> Server: ... -if sys.platform != "win32": - if sys.version_info >= (3, 10): - async def open_unix_connection( - path: StrPath | None = None, *, limit: int = 65536, **kwds: Any - ) -> tuple[StreamReader, StreamWriter]: ... - async def start_unix_server( - client_connected_cb: _ClientConnectedCallback, path: StrPath | None = None, *, limit: int = 65536, **kwds: Any - ) -> Server: ... - else: - async def open_unix_connection( - path: StrPath | None = None, *, loop: events.AbstractEventLoop | None = None, limit: int = 65536, **kwds: Any - ) -> tuple[StreamReader, StreamWriter]: ... - async def start_unix_server( - client_connected_cb: _ClientConnectedCallback, - path: StrPath | None = None, - *, - loop: events.AbstractEventLoop | None = None, - limit: int = 65536, - **kwds: Any, - ) -> Server: ... - class FlowControlMixin(protocols.Protocol): def __init__(self, loop: events.AbstractEventLoop | None = None) -> None: ... @@ -141,7 +106,7 @@ class StreamWriter: class StreamReader: def __init__(self, limit: int = 65536, loop: events.AbstractEventLoop | None = None) -> None: ... - def exception(self) -> Exception: ... + def exception(self) -> Exception | None: ... def set_exception(self, exc: Exception) -> None: ... def set_transport(self, transport: transports.BaseTransport) -> None: ... def feed_eof(self) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/subprocess.pyi b/mypy/typeshed/stdlib/asyncio/subprocess.pyi index ceee2b5b90a09..6405e5ae14741 100644 --- a/mypy/typeshed/stdlib/asyncio/subprocess.pyi +++ b/mypy/typeshed/stdlib/asyncio/subprocess.pyi @@ -101,7 +101,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, ) -> Process: ... -elif sys.version_info >= (3, 10): +else: async def create_subprocess_shell( cmd: str | bytes, stdin: int | IO[Any] | None = None, @@ -164,67 +164,3 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> Process: ... - -else: # >= 3.9 - async def create_subprocess_shell( - cmd: str | bytes, - stdin: int | IO[Any] | None = None, - stdout: int | IO[Any] | None = None, - stderr: int | IO[Any] | None = None, - loop: events.AbstractEventLoop | None = None, - limit: int = 65536, - *, - # These parameters are forced to these values by BaseEventLoop.subprocess_shell - universal_newlines: Literal[False] = False, - shell: Literal[True] = True, - bufsize: Literal[0] = 0, - encoding: None = None, - errors: None = None, - text: Literal[False] | None = None, - # These parameters are taken by subprocess.Popen, which this ultimately delegates to - executable: StrOrBytesPath | None = None, - preexec_fn: Callable[[], Any] | None = None, - close_fds: bool = True, - cwd: StrOrBytesPath | None = None, - env: subprocess._ENV | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - group: None | str | int = None, - extra_groups: None | Collection[str | int] = None, - user: None | str | int = None, - umask: int = -1, - ) -> Process: ... - async def create_subprocess_exec( - program: StrOrBytesPath, - *args: StrOrBytesPath, - stdin: int | IO[Any] | None = None, - stdout: int | IO[Any] | None = None, - stderr: int | IO[Any] | None = None, - loop: events.AbstractEventLoop | None = None, - limit: int = 65536, - # These parameters are forced to these values by BaseEventLoop.subprocess_exec - universal_newlines: Literal[False] = False, - shell: Literal[False] = False, - bufsize: Literal[0] = 0, - encoding: None = None, - errors: None = None, - text: Literal[False] | None = None, - # These parameters are taken by subprocess.Popen, which this ultimately delegates to - executable: StrOrBytesPath | None = None, - preexec_fn: Callable[[], Any] | None = None, - close_fds: bool = True, - cwd: StrOrBytesPath | None = None, - env: subprocess._ENV | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - group: None | str | int = None, - extra_groups: None | Collection[str | int] = None, - user: None | str | int = None, - umask: int = -1, - ) -> Process: ... diff --git a/mypy/typeshed/stdlib/asyncio/taskgroups.pyi b/mypy/typeshed/stdlib/asyncio/taskgroups.pyi index 2968b07197614..886a79c4beb5a 100644 --- a/mypy/typeshed/stdlib/asyncio/taskgroups.pyi +++ b/mypy/typeshed/stdlib/asyncio/taskgroups.pyi @@ -37,3 +37,5 @@ class TaskGroup: ) -> Task[_T]: ... def _on_task_done(self, task: Task[object]) -> None: ... + if sys.version_info >= (3, 15): + def cancel(self) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/tasks.pyi b/mypy/typeshed/stdlib/asyncio/tasks.pyi index 06e9f381a89ff..66c31f15e6fb1 100644 --- a/mypy/typeshed/stdlib/asyncio/tasks.pyi +++ b/mypy/typeshed/stdlib/asyncio/tasks.pyi @@ -8,8 +8,7 @@ from _asyncio import ( _unregister_task as _unregister_task, ) from collections.abc import AsyncIterator, Awaitable, Coroutine, Generator, Iterable, Iterator -from typing import Any, Final, Literal, Protocol, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, Protocol, TypeAlias, TypeVar, overload, type_check_only from . import _CoroutineLike from .events import AbstractEventLoop @@ -88,17 +87,12 @@ ALL_COMPLETED: Final = concurrent.futures.ALL_COMPLETED if sys.version_info >= (3, 13): @type_check_only - class _SyncAndAsyncIterator(Iterator[_T_co], AsyncIterator[_T_co], Protocol[_T_co]): ... + class _SyncAndAsyncIterator(Iterator[Coroutine[Any, Any, _T]], AsyncIterator[Future[_T]], Protocol[_T]): ... - def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> _SyncAndAsyncIterator[Future[_T]]: ... - -elif sys.version_info >= (3, 10): - def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> Iterator[Future[_T]]: ... + def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> _SyncAndAsyncIterator[_T]: ... else: - def as_completed( - fs: Iterable[_FutureLike[_T]], *, loop: AbstractEventLoop | None = None, timeout: float | None = None - ) -> Iterator[Future[_T]]: ... + def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> Iterator[Future[_T]]: ... @overload def ensure_future(coro_or_future: _FT, *, loop: AbstractEventLoop | None = None) -> _FT: ... # type: ignore[overload-overlap] @@ -111,269 +105,133 @@ def ensure_future(coro_or_future: Awaitable[_T], *, loop: AbstractEventLoop | No # typing PR #1550 for discussion. # # N.B. Having overlapping overloads is the only way to get acceptable type inference in all edge cases. -if sys.version_info >= (3, 10): - @overload - def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: Literal[False] = False) -> Future[tuple[_T1]]: ... # type: ignore[overload-overlap] - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: Literal[False] = False - ) -> Future[tuple[_T1, _T2]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - /, - *, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - /, - *, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - /, - *, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - coro_or_future6: _FutureLike[_T6], - /, - *, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... - @overload - def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: Literal[False] = False) -> Future[list[_T]]: ... # type: ignore[overload-overlap] - @overload - def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]: ... - @overload - def gather( - coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: bool - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]: ... - @overload - def gather( - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - /, - *, - return_exceptions: bool, - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]: ... - @overload - def gather( - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - /, - *, - return_exceptions: bool, - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]: ... - @overload - def gather( - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - /, - *, - return_exceptions: bool, - ) -> Future[ - tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException] - ]: ... - @overload - def gather( - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - coro_or_future6: _FutureLike[_T6], - /, - *, - return_exceptions: bool, - ) -> Future[ - tuple[ - _T1 | BaseException, - _T2 | BaseException, - _T3 | BaseException, - _T4 | BaseException, - _T5 | BaseException, - _T6 | BaseException, - ] - ]: ... - @overload - def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]: ... - -else: - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], /, *, loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False - ) -> Future[tuple[_T1]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - coro_or_future6: _FutureLike[_T6], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: Literal[False] = False, - ) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... - @overload - def gather( # type: ignore[overload-overlap] - *coros_or_futures: _FutureLike[_T], loop: AbstractEventLoop | None = None, return_exceptions: Literal[False] = False - ) -> Future[list[_T]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], /, *, loop: AbstractEventLoop | None = None, return_exceptions: bool - ) -> Future[tuple[_T1 | BaseException]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: bool, - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: bool, - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: bool, - ) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]: ... - @overload - def gather( # type: ignore[overload-overlap] - coro_or_future1: _FutureLike[_T1], - coro_or_future2: _FutureLike[_T2], - coro_or_future3: _FutureLike[_T3], - coro_or_future4: _FutureLike[_T4], - coro_or_future5: _FutureLike[_T5], - coro_or_future6: _FutureLike[_T6], - /, - *, - loop: AbstractEventLoop | None = None, - return_exceptions: bool, - ) -> Future[ - tuple[ - _T1 | BaseException, - _T2 | BaseException, - _T3 | BaseException, - _T4 | BaseException, - _T5 | BaseException, - _T6 | BaseException, - ] - ]: ... - @overload - def gather( - *coros_or_futures: _FutureLike[_T], loop: AbstractEventLoop | None = None, return_exceptions: bool - ) -> Future[list[_T | BaseException]]: ... +@overload +def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: Literal[False] = False) -> Future[tuple[_T1]]: ... # type: ignore[overload-overlap] +@overload +def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: Literal[False] = False +) -> Future[tuple[_T1, _T2]]: ... +@overload +def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, + *, + return_exceptions: Literal[False] = False, +) -> Future[tuple[_T1, _T2, _T3]]: ... +@overload +def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, + *, + return_exceptions: Literal[False] = False, +) -> Future[tuple[_T1, _T2, _T3, _T4]]: ... +@overload +def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + /, + *, + return_exceptions: Literal[False] = False, +) -> Future[tuple[_T1, _T2, _T3, _T4, _T5]]: ... +@overload +def gather( # type: ignore[overload-overlap] + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, + *, + return_exceptions: Literal[False] = False, +) -> Future[tuple[_T1, _T2, _T3, _T4, _T5, _T6]]: ... +@overload +def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: Literal[False] = False) -> Future[list[_T]]: ... # type: ignore[overload-overlap] +@overload +def gather(coro_or_future1: _FutureLike[_T1], /, *, return_exceptions: bool) -> Future[tuple[_T1 | BaseException]]: ... +@overload +def gather( + coro_or_future1: _FutureLike[_T1], coro_or_future2: _FutureLike[_T2], /, *, return_exceptions: bool +) -> Future[tuple[_T1 | BaseException, _T2 | BaseException]]: ... +@overload +def gather( + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + /, + *, + return_exceptions: bool, +) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException]]: ... +@overload +def gather( + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + /, + *, + return_exceptions: bool, +) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException]]: ... +@overload +def gather( + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + /, + *, + return_exceptions: bool, +) -> Future[tuple[_T1 | BaseException, _T2 | BaseException, _T3 | BaseException, _T4 | BaseException, _T5 | BaseException]]: ... +@overload +def gather( + coro_or_future1: _FutureLike[_T1], + coro_or_future2: _FutureLike[_T2], + coro_or_future3: _FutureLike[_T3], + coro_or_future4: _FutureLike[_T4], + coro_or_future5: _FutureLike[_T5], + coro_or_future6: _FutureLike[_T6], + /, + *, + return_exceptions: bool, +) -> Future[ + tuple[ + _T1 | BaseException, + _T2 | BaseException, + _T3 | BaseException, + _T4 | BaseException, + _T5 | BaseException, + _T6 | BaseException, + ] +]: ... +@overload +def gather(*coros_or_futures: _FutureLike[_T], return_exceptions: bool) -> Future[list[_T | BaseException]]: ... # unlike some asyncio apis, This does strict runtime checking of actually being a coroutine, not of any future-like. def run_coroutine_threadsafe(coro: Coroutine[Any, Any, _T], loop: AbstractEventLoop) -> concurrent.futures.Future[_T]: ... +def shield(arg: _FutureLike[_T]) -> Future[_T]: ... -if sys.version_info >= (3, 10): - def shield(arg: _FutureLike[_T]) -> Future[_T]: ... - @overload - async def sleep(delay: float) -> None: ... - @overload - async def sleep(delay: float, result: _T) -> _T: ... - async def wait_for(fut: _FutureLike[_T], timeout: float | None) -> _T: ... +@overload +async def sleep(delay: float) -> None: ... +@overload +async def sleep(delay: float, result: _T) -> _T: ... -else: - def shield(arg: _FutureLike[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]: ... - @overload - async def sleep(delay: float, *, loop: AbstractEventLoop | None = None) -> None: ... - @overload - async def sleep(delay: float, result: _T, *, loop: AbstractEventLoop | None = None) -> _T: ... - async def wait_for(fut: _FutureLike[_T], timeout: float | None, *, loop: AbstractEventLoop | None = None) -> _T: ... +async def wait_for(fut: _FutureLike[_T], timeout: float | None) -> _T: ... if sys.version_info >= (3, 11): async def wait( fs: Iterable[_FT], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" ) -> tuple[set[_FT], set[_FT]]: ... -elif sys.version_info >= (3, 10): +else: @overload async def wait( # type: ignore[overload-overlap] fs: Iterable[_FT], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" @@ -383,24 +241,6 @@ elif sys.version_info >= (3, 10): fs: Iterable[Awaitable[_T]], *, timeout: float | None = None, return_when: str = "ALL_COMPLETED" ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... -else: - @overload - async def wait( # type: ignore[overload-overlap] - fs: Iterable[_FT], - *, - loop: AbstractEventLoop | None = None, - timeout: float | None = None, - return_when: str = "ALL_COMPLETED", - ) -> tuple[set[_FT], set[_FT]]: ... - @overload - async def wait( - fs: Iterable[Awaitable[_T]], - *, - loop: AbstractEventLoop | None = None, - timeout: float | None = None, - return_when: str = "ALL_COMPLETED", - ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... - if sys.version_info >= (3, 12): _TaskCompatibleCoro: TypeAlias = Coroutine[Any, Any, _T_co] else: diff --git a/mypy/typeshed/stdlib/asyncio/threads.pyi b/mypy/typeshed/stdlib/asyncio/threads.pyi index 00aae2ea814cb..f1d8829180982 100644 --- a/mypy/typeshed/stdlib/asyncio/threads.pyi +++ b/mypy/typeshed/stdlib/asyncio/threads.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import TypeVar -from typing_extensions import ParamSpec +from typing import ParamSpec, TypeVar # Keep asyncio.__all__ updated with any changes to __all__ here __all__ = ("to_thread",) diff --git a/mypy/typeshed/stdlib/asyncio/tools.pyi b/mypy/typeshed/stdlib/asyncio/tools.pyi index bc8b809b9c055..36c65541c9cd0 100644 --- a/mypy/typeshed/stdlib/asyncio/tools.pyi +++ b/mypy/typeshed/stdlib/asyncio/tools.pyi @@ -42,5 +42,10 @@ def build_task_table(result: Iterable[_AwaitedInfo]) -> list[list[int | str]]: . if sys.version_info >= (3, 14): def exit_with_permission_help_text() -> None: ... -def display_awaited_by_tasks_table(pid: SupportsIndex) -> None: ... -def display_awaited_by_tasks_tree(pid: SupportsIndex) -> None: ... +if sys.version_info >= (3, 15): + def display_awaited_by_tasks_table(pid: SupportsIndex, retries: SupportsIndex = 3) -> None: ... + def display_awaited_by_tasks_tree(pid: SupportsIndex, retries: SupportsIndex = 3) -> None: ... + +else: + def display_awaited_by_tasks_table(pid: SupportsIndex) -> None: ... + def display_awaited_by_tasks_tree(pid: SupportsIndex) -> None: ... diff --git a/mypy/typeshed/stdlib/asyncio/trsock.pyi b/mypy/typeshed/stdlib/asyncio/trsock.pyi index d3e95559ebefa..f621240d668a2 100644 --- a/mypy/typeshed/stdlib/asyncio/trsock.pyi +++ b/mypy/typeshed/stdlib/asyncio/trsock.pyi @@ -4,8 +4,8 @@ from _typeshed import ReadableBuffer from builtins import type as Type # alias to avoid name clashes with property named "type" from collections.abc import Iterable from types import TracebackType -from typing import Any, BinaryIO, NoReturn, overload -from typing_extensions import TypeAlias, deprecated +from typing import Any, BinaryIO, NoReturn, TypeAlias, overload +from typing_extensions import deprecated # These are based in socket, maybe move them out into _typeshed.pyi or such _Address: TypeAlias = socket._Address @@ -27,14 +27,17 @@ class TransportSocket: def dup(self) -> socket.socket: ... def get_inheritable(self) -> bool: ... def shutdown(self, how: int) -> None: ... + @overload def getsockopt(self, level: int, optname: int) -> int: ... @overload def getsockopt(self, level: int, optname: int, buflen: int) -> bytes: ... + @overload def setsockopt(self, level: int, optname: int, value: int | ReadableBuffer) -> None: ... @overload def setsockopt(self, level: int, optname: int, value: None, optlen: int) -> None: ... + def getpeername(self) -> _RetAddress: ... def getsockname(self) -> _RetAddress: ... def getsockbyname(self) -> NoReturn: ... # This method doesn't exist on socket, yet is passed through? @@ -51,6 +54,7 @@ class TransportSocket: def connect_ex(self, address: _Address) -> int: ... @deprecated("Removed in Python 3.11") def bind(self, address: _Address) -> None: ... + if sys.platform == "win32": @deprecated("Removed in Python 3.11") def ioctl(self, control: int, option: int | tuple[int, int, int] | bool) -> None: ... @@ -68,6 +72,7 @@ class TransportSocket: def close(self) -> None: ... @deprecated("Removed in Python 3.11") def detach(self) -> int: ... + if sys.platform == "linux": @deprecated("Removed in Python 3.11") def sendmsg_afalg( @@ -88,18 +93,21 @@ class TransportSocket: address: _Address | None = None, /, ) -> int: ... + @overload @deprecated("Removed in Python 3.11.") def sendto(self, data: ReadableBuffer, address: _Address) -> int: ... @overload @deprecated("Removed in Python 3.11.") def sendto(self, data: ReadableBuffer, flags: int, address: _Address) -> int: ... + @deprecated("Removed in Python 3.11.") def send(self, data: ReadableBuffer, flags: int = 0) -> int: ... @deprecated("Removed in Python 3.11.") def sendall(self, data: ReadableBuffer, flags: int = 0) -> None: ... @deprecated("Removed in Python 3.11.") def set_inheritable(self, inheritable: bool) -> None: ... + if sys.platform == "win32": @deprecated("Removed in Python 3.11.") def share(self, process_id: int) -> bytes: ... diff --git a/mypy/typeshed/stdlib/asyncio/unix_events.pyi b/mypy/typeshed/stdlib/asyncio/unix_events.pyi index 54a2a039749e3..368cac38302f6 100644 --- a/mypy/typeshed/stdlib/asyncio/unix_events.pyi +++ b/mypy/typeshed/stdlib/asyncio/unix_events.pyi @@ -95,6 +95,7 @@ if sys.platform != "win32": if sys.version_info >= (3, 12): # Doesn't actually have ABCMeta metaclass at runtime, but mypy complains if we don't have it in the stub. # See discussion in #7412 + @deprecated("Deprecated since Python 3.12; removed in Python 3.14.") class BaseChildWatcher(AbstractChildWatcher, metaclass=ABCMeta): def close(self) -> None: ... def is_active(self) -> bool: ... diff --git a/mypy/typeshed/stdlib/asyncore.pyi b/mypy/typeshed/stdlib/asyncore.pyi index 36d1862fdda78..96f9f637c7333 100644 --- a/mypy/typeshed/stdlib/asyncore.pyi +++ b/mypy/typeshed/stdlib/asyncore.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import FileDescriptorLike, ReadableBuffer from socket import socket -from typing import Any, overload -from typing_extensions import TypeAlias +from typing import Any, TypeAlias, overload # cyclic dependence with asynchat _MapType: TypeAlias = dict[int, Any] @@ -75,10 +74,12 @@ if sys.platform != "win32": def __init__(self, fd: int) -> None: ... def recv(self, bufsize: int, flags: int = ...) -> bytes: ... def send(self, data: bytes, flags: int = ...) -> int: ... + @overload def getsockopt(self, level: int, optname: int, buflen: None = None) -> int: ... @overload def getsockopt(self, level: int, optname: int, buflen: int) -> bytes: ... + def read(self, bufsize: int, flags: int = ...) -> bytes: ... def write(self, data: bytes, flags: int = ...) -> int: ... def close(self) -> None: ... diff --git a/mypy/typeshed/stdlib/atexit.pyi b/mypy/typeshed/stdlib/atexit.pyi index 7f7b05ccc0a39..9177d80169be2 100644 --- a/mypy/typeshed/stdlib/atexit.pyi +++ b/mypy/typeshed/stdlib/atexit.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import TypeVar -from typing_extensions import ParamSpec +from typing import ParamSpec, TypeVar _T = TypeVar("_T") _P = ParamSpec("_P") diff --git a/mypy/typeshed/stdlib/audioop.pyi b/mypy/typeshed/stdlib/audioop.pyi index f3ce78ccb7fae..a7d5e8adb7a52 100644 --- a/mypy/typeshed/stdlib/audioop.pyi +++ b/mypy/typeshed/stdlib/audioop.pyi @@ -1,4 +1,5 @@ -from typing_extensions import Buffer, TypeAlias +from typing import TypeAlias +from typing_extensions import Buffer _AdpcmState: TypeAlias = tuple[int, int] _RatecvState: TypeAlias = tuple[int, tuple[tuple[int, int], ...]] diff --git a/mypy/typeshed/stdlib/base64.pyi b/mypy/typeshed/stdlib/base64.pyi index 279d74a94ebe2..dd4782142852f 100644 --- a/mypy/typeshed/stdlib/base64.pyi +++ b/mypy/typeshed/stdlib/base64.pyi @@ -1,6 +1,5 @@ import sys -from _typeshed import ReadableBuffer -from typing import IO +from _typeshed import ReadableBuffer, SupportsNoArgReadline, SupportsRead, SupportsWrite __all__ = [ "encode", @@ -13,6 +12,8 @@ __all__ = [ "b32decode", "b16encode", "b16decode", + "b32hexencode", + "b32hexdecode", "b85encode", "b85decode", "a85encode", @@ -23,39 +24,101 @@ __all__ = [ "urlsafe_b64decode", ] -if sys.version_info >= (3, 10): - __all__ += ["b32hexencode", "b32hexdecode"] if sys.version_info >= (3, 13): __all__ += ["z85decode", "z85encode"] -def b64encode(s: ReadableBuffer, altchars: ReadableBuffer | None = None) -> bytes: ... -def b64decode(s: str | ReadableBuffer, altchars: str | ReadableBuffer | None = None, validate: bool = False) -> bytes: ... +if sys.version_info >= (3, 15): + def b64encode( + s: ReadableBuffer, altchars: ReadableBuffer | None = None, *, padded: bool = True, wrapcol: int = 0 + ) -> bytes: ... + def b64decode( + s: str | ReadableBuffer, + altchars: str | ReadableBuffer | None = None, + validate: bool = ..., + *, + padded: bool = True, + ignorechars: ReadableBuffer = ..., + canonical: bool = False, + ) -> bytes: ... + +else: + def b64encode(s: ReadableBuffer, altchars: ReadableBuffer | None = None) -> bytes: ... + def b64decode(s: str | ReadableBuffer, altchars: str | ReadableBuffer | None = None, validate: bool = False) -> bytes: ... + def standard_b64encode(s: ReadableBuffer) -> bytes: ... def standard_b64decode(s: str | ReadableBuffer) -> bytes: ... -def urlsafe_b64encode(s: ReadableBuffer) -> bytes: ... -def urlsafe_b64decode(s: str | ReadableBuffer) -> bytes: ... -def b32encode(s: ReadableBuffer) -> bytes: ... -def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: str | ReadableBuffer | None = None) -> bytes: ... -def b16encode(s: ReadableBuffer) -> bytes: ... -def b16decode(s: str | ReadableBuffer, casefold: bool = False) -> bytes: ... - -if sys.version_info >= (3, 10): + +if sys.version_info >= (3, 15): + def urlsafe_b64encode(s: ReadableBuffer, *, padded: bool = True) -> bytes: ... + def urlsafe_b64decode(s: str | ReadableBuffer, *, padded: bool = False) -> bytes: ... + def b32encode(s: ReadableBuffer, *, padded: bool = True, wrapcol: int = 0) -> bytes: ... + def b32decode( + s: str | ReadableBuffer, + casefold: bool = False, + map01: str | ReadableBuffer | None = None, + *, + padded: bool = True, + ignorechars: ReadableBuffer = b"", + canonical: bool = False, + ) -> bytes: ... + def b16encode(s: ReadableBuffer, *, wrapcol: int = 0) -> bytes: ... + def b16decode(s: str | ReadableBuffer, casefold: bool = False, *, ignorechars: ReadableBuffer = b"") -> bytes: ... + +else: + def urlsafe_b64encode(s: ReadableBuffer) -> bytes: ... + def urlsafe_b64decode(s: str | ReadableBuffer) -> bytes: ... + def b32encode(s: ReadableBuffer) -> bytes: ... + def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: str | ReadableBuffer | None = None) -> bytes: ... + def b16encode(s: ReadableBuffer) -> bytes: ... + def b16decode(s: str | ReadableBuffer, casefold: bool = False) -> bytes: ... + +if sys.version_info >= (3, 15): + def b32hexencode(s: ReadableBuffer, *, padded: bool = True, wrapcol: int = 0) -> bytes: ... + def b32hexdecode( + s: str | ReadableBuffer, + casefold: bool = False, + *, + padded: bool = True, + ignorechars: ReadableBuffer = b"", + canonical: bool = False, + ) -> bytes: ... + +else: def b32hexencode(s: ReadableBuffer) -> bytes: ... def b32hexdecode(s: str | ReadableBuffer, casefold: bool = False) -> bytes: ... def a85encode( b: ReadableBuffer, *, foldspaces: bool = False, wrapcol: int = 0, pad: bool = False, adobe: bool = False ) -> bytes: ... -def a85decode( - b: str | ReadableBuffer, *, foldspaces: bool = False, adobe: bool = False, ignorechars: bytearray | bytes = b" \t\n\r\x0b" -) -> bytes: ... -def b85encode(b: ReadableBuffer, pad: bool = False) -> bytes: ... -def b85decode(b: str | ReadableBuffer) -> bytes: ... -def decode(input: IO[bytes], output: IO[bytes]) -> None: ... -def encode(input: IO[bytes], output: IO[bytes]) -> None: ... + +if sys.version_info >= (3, 15): + def a85decode( + b: str | ReadableBuffer, + *, + foldspaces: bool = False, + adobe: bool = False, + ignorechars: bytearray | bytes = b" \t\n\r\x0b", + canonical: bool = False, + ) -> bytes: ... + def b85encode(b: ReadableBuffer, pad: bool = False, *, wrapcol: int = 0) -> bytes: ... + def b85decode(b: str | ReadableBuffer, *, ignorechars: ReadableBuffer = b"", canonical: bool = False) -> bytes: ... + +else: + def a85decode( + b: str | ReadableBuffer, *, foldspaces: bool = False, adobe: bool = False, ignorechars: bytearray | bytes = b" \t\n\r\x0b" + ) -> bytes: ... + def b85encode(b: ReadableBuffer, pad: bool = False) -> bytes: ... + def b85decode(b: str | ReadableBuffer) -> bytes: ... + +def decode(input: SupportsNoArgReadline[bytes], output: SupportsWrite[bytes]) -> None: ... +def encode(input: SupportsRead[bytes], output: SupportsWrite[bytes]) -> None: ... def encodebytes(s: ReadableBuffer) -> bytes: ... def decodebytes(s: ReadableBuffer) -> bytes: ... if sys.version_info >= (3, 13): - def z85encode(s: ReadableBuffer) -> bytes: ... - def z85decode(s: str | ReadableBuffer) -> bytes: ... + if sys.version_info >= (3, 15): + def z85encode(s: ReadableBuffer, pad: bool = False, *, wrapcol: int = 0) -> bytes: ... + def z85decode(s: str | ReadableBuffer, *, ignorechars: ReadableBuffer = b"", canonical: bool = False) -> bytes: ... + else: + def z85encode(s: ReadableBuffer) -> bytes: ... + def z85decode(s: str | ReadableBuffer) -> bytes: ... diff --git a/mypy/typeshed/stdlib/bdb.pyi b/mypy/typeshed/stdlib/bdb.pyi index b6be2210ffe2e..c2c45e2684b4b 100644 --- a/mypy/typeshed/stdlib/bdb.pyi +++ b/mypy/typeshed/stdlib/bdb.pyi @@ -1,10 +1,9 @@ import sys -from _typeshed import ExcInfo, TraceFunction, Unused +from _typeshed import ExcInfo, ReadableBuffer, TraceFunction, Unused from collections.abc import Callable, Iterable, Iterator, Mapping from contextlib import contextmanager from types import CodeType, FrameType, TracebackType -from typing import IO, Any, Final, Literal, SupportsInt, TypeVar -from typing_extensions import ParamSpec, TypeAlias +from typing import IO, Any, Final, Literal, ParamSpec, SupportsInt, TypeAlias, TypeVar __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -85,11 +84,21 @@ class Bdb: def get_all_breaks(self) -> dict[str, list[int]]: ... def get_stack(self, f: FrameType | None, t: TracebackType | None) -> tuple[list[tuple[FrameType, int]], int]: ... def format_stack_entry(self, frame_lineno: tuple[FrameType, int], lprefix: str = ": ") -> str: ... - def run( - self, cmd: str | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None + def run( # matches `builtins.exec` + self, + cmd: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, ) -> None: ... - def runeval(self, expr: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None: ... - def runctx(self, cmd: str | CodeType, globals: dict[str, Any] | None, locals: Mapping[str, Any] | None) -> None: ... + def runctx( # matches `builtins.exec` + self, cmd: str | ReadableBuffer | CodeType, globals: dict[str, Any] | None, locals: Mapping[str, object] | None + ) -> None: ... + def runeval( # matches `builtins.eval` + self, + expr: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + ) -> Any: ... def runcall(self, func: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... if sys.version_info >= (3, 14): def start_trace(self) -> None: ... diff --git a/mypy/typeshed/stdlib/binascii.pyi b/mypy/typeshed/stdlib/binascii.pyi index 5606d5cdf74d9..6840c68838119 100644 --- a/mypy/typeshed/stdlib/binascii.pyi +++ b/mypy/typeshed/stdlib/binascii.pyi @@ -1,6 +1,7 @@ import sys from _typeshed import ReadableBuffer -from typing_extensions import TypeAlias, deprecated +from typing import TypeAlias +from typing_extensions import deprecated # Many functions in binascii accept buffer objects # or ASCII-only strings. @@ -9,13 +10,68 @@ _AsciiBuffer: TypeAlias = str | ReadableBuffer def a2b_uu(data: _AsciiBuffer, /) -> bytes: ... def b2a_uu(data: ReadableBuffer, /, *, backtick: bool = False) -> bytes: ... -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 15): + ASCII85_ALPHABET: bytes + BINHEX_ALPHABET: bytes + CRYPT_ALPHABET: bytes + UU_ALPHABET: bytes + BASE64_ALPHABET: bytes + URLSAFE_BASE64_ALPHABET: bytes + BASE32_ALPHABET: bytes + BASE32HEX_ALPHABET: bytes + BASE85_ALPHABET: bytes + Z85_ALPHABET: bytes + def a2b_base64( + data: _AsciiBuffer, + /, + *, + strict_mode: bool = False, + alphabet: bytes = ..., + padded: bool = True, + ignorechars: ReadableBuffer = ..., + canonical: bool = False, + ) -> bytes: ... + def b2a_base64( + data: ReadableBuffer, /, *, newline: bool = True, alphabet: ReadableBuffer = ..., padded: bool = True, wrapcol: int = 0 + ) -> bytes: ... + def b2a_base32( + data: ReadableBuffer, /, *, alphabet: ReadableBuffer = ..., padded: bool = True, wrapcol: int = 0 + ) -> bytes: ... + def a2b_base32( + data: _AsciiBuffer, + /, + *, + alphabet: bytes = ..., + padded: bool = True, + ignorechars: ReadableBuffer = b"", + canonical: bool = False, + ) -> bytes: ... + def b2a_ascii85( + data: ReadableBuffer, /, *, foldspaces: bool = False, wrapcol: int = 0, pad: bool = False, adobe: bool = False + ) -> bytes: ... + def a2b_ascii85( + data: _AsciiBuffer, + /, + *, + foldspaces: bool = False, + adobe: bool = False, + ignorechars: ReadableBuffer = b"", + canonical: bool = False, + ) -> bytes: ... + def b2a_base85(data: ReadableBuffer, /, *, alphabet: ReadableBuffer = ..., pad: bool = False, wrapcol: int = 0) -> bytes: ... + def a2b_base85( + data: _AsciiBuffer, /, *, alphabet: bytes = ..., ignorechars: ReadableBuffer = b"", canonical: bool = False + ) -> bytes: ... + +elif sys.version_info >= (3, 11): def a2b_base64(data: _AsciiBuffer, /, *, strict_mode: bool = False) -> bytes: ... else: def a2b_base64(data: _AsciiBuffer, /) -> bytes: ... -def b2a_base64(data: ReadableBuffer, /, *, newline: bool = True) -> bytes: ... +if sys.version_info < (3, 15): + def b2a_base64(data: ReadableBuffer, /, *, newline: bool = True) -> bytes: ... + def a2b_qp(data: _AsciiBuffer, header: bool = False) -> bytes: ... def b2a_qp(data: ReadableBuffer, quotetabs: bool = False, istext: bool = True, header: bool = False) -> bytes: ... @@ -33,8 +89,14 @@ def crc_hqx(data: ReadableBuffer, crc: int, /) -> int: ... def crc32(data: ReadableBuffer, crc: int = 0, /) -> int: ... def b2a_hex(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = 1) -> bytes: ... def hexlify(data: ReadableBuffer, sep: str | bytes = ..., bytes_per_sep: int = 1) -> bytes: ... -def a2b_hex(hexstr: _AsciiBuffer, /) -> bytes: ... -def unhexlify(hexstr: _AsciiBuffer, /) -> bytes: ... + +if sys.version_info >= (3, 15): + def a2b_hex(hexstr: _AsciiBuffer, /, *, ignorechars: ReadableBuffer = b"") -> bytes: ... + def unhexlify(hexstr: _AsciiBuffer, /, *, ignorechars: ReadableBuffer = b"") -> bytes: ... + +else: + def a2b_hex(hexstr: _AsciiBuffer, /) -> bytes: ... + def unhexlify(hexstr: _AsciiBuffer, /) -> bytes: ... class Error(ValueError): ... class Incomplete(Exception): ... diff --git a/mypy/typeshed/stdlib/binhex.pyi b/mypy/typeshed/stdlib/binhex.pyi index bdead928468f4..f309f4e026a59 100644 --- a/mypy/typeshed/stdlib/binhex.pyi +++ b/mypy/typeshed/stdlib/binhex.pyi @@ -1,6 +1,5 @@ from _typeshed import SizedBuffer -from typing import IO, Any, Final -from typing_extensions import TypeAlias +from typing import IO, Any, Final, TypeAlias __all__ = ["binhex", "hexbin", "Error"] diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi index 25b21ba971540..8497f9e2e1037 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -33,20 +33,22 @@ from _typeshed import ( from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper from os import PathLike -from types import CellType, CodeType, GenericAlias, TracebackType +from types import CellType, CodeType, EllipsisType, GenericAlias, NotImplementedType, TracebackType, UnionType # mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} # are imported from collections.abc in builtins.pyi -from typing import ( # noqa: Y022,UP035,RUF100 +from typing import ( # noqa: Y022,UP035 IO, Any, BinaryIO, ClassVar, + Concatenate, Final, Generic, Mapping, MutableMapping, MutableSequence, + ParamSpec, Protocol, Sequence, SupportsAbs, @@ -54,6 +56,8 @@ from typing import ( # noqa: Y022,UP035,RUF100 SupportsComplex, SupportsFloat, SupportsIndex, + TypeAlias, + TypeGuard, TypeVar, final, overload, @@ -61,18 +65,7 @@ from typing import ( # noqa: Y022,UP035,RUF100 ) # we can't import `Literal` from typing or mypy crashes: see #11247 -from typing_extensions import ( # noqa: Y023 - Concatenate, - Literal, - ParamSpec, - Self, - TypeAlias, - TypeGuard, - TypeIs, - TypeVarTuple, - deprecated, - disjoint_base, -) +from typing_extensions import Literal, Self, TypeIs, TypeVarTuple, deprecated, disjoint_base # noqa: Y023, UP035 if sys.version_info >= (3, 14): from _typeshed import AnnotateFunc @@ -110,10 +103,12 @@ class object: __dict__: dict[str, Any] __module__: str __annotations__: dict[str, Any] + @property def __class__(self) -> type[Self]: ... @__class__.setter def __class__(self, type: type[Self], /) -> None: ... + def __init__(self) -> None: ... def __new__(cls) -> Self: ... # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers. @@ -142,41 +137,43 @@ class object: @disjoint_base class staticmethod(Generic[_P, _R_co]): + __name__: str + __qualname__: str @property def __func__(self) -> Callable[_P, _R_co]: ... @property def __isabstractmethod__(self) -> bool: ... def __init__(self, f: Callable[_P, _R_co], /) -> None: ... + @overload def __get__(self, instance: None, owner: type, /) -> Callable[_P, _R_co]: ... @overload def __get__(self, instance: _T, owner: type[_T] | None = None, /) -> Callable[_P, _R_co]: ... - if sys.version_info >= (3, 10): - __name__: str - __qualname__: str - @property - def __wrapped__(self) -> Callable[_P, _R_co]: ... - def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... + + @property + def __wrapped__(self) -> Callable[_P, _R_co]: ... + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... if sys.version_info >= (3, 14): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... __annotate__: AnnotateFunc | None @disjoint_base class classmethod(Generic[_T, _P, _R_co]): + __name__: str + __qualname__: str @property def __func__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... @property def __isabstractmethod__(self) -> bool: ... def __init__(self, f: Callable[Concatenate[type[_T], _P], _R_co], /) -> None: ... + @overload def __get__(self, instance: _T, owner: type[_T] | None = None, /) -> Callable[_P, _R_co]: ... @overload def __get__(self, instance: None, owner: type[_T], /) -> Callable[_P, _R_co]: ... - if sys.version_info >= (3, 10): - __name__: str - __qualname__: str - @property - def __wrapped__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... + + @property + def __wrapped__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ... if sys.version_info >= (3, 14): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... __annotate__: AnnotateFunc | None @@ -207,16 +204,19 @@ class type: def __text_signature__(self) -> str | None: ... @property def __weakrefoffset__(self) -> int: ... + @overload def __init__(self, o: object, /) -> None: ... @overload def __init__(self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None: ... + @overload def __new__(cls, o: object, /) -> type: ... @overload def __new__( cls: type[_typeshed.Self], name: str, bases: tuple[type, ...], namespace: dict[str, Any], /, **kwds: Any ) -> _typeshed.Self: ... + def __call__(self, *args: Any, **kwds: Any) -> Any: ... def __subclasses__(self: _typeshed.Self) -> list[_typeshed.Self]: ... # Note: the documentation doesn't specify what the return type is, the standard @@ -226,11 +226,10 @@ class type: def __subclasscheck__(self, subclass: type, /) -> bool: ... @classmethod def __prepare__(metacls, name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]: ... - if sys.version_info >= (3, 10): - # `int | str` produces an instance of `UnionType`, but `int | int` produces an instance of `type`, - # and `abc.ABC | abc.ABC` produces an instance of `abc.ABCMeta`. - def __or__(self: _typeshed.Self, value: Any, /) -> types.UnionType | _typeshed.Self: ... - def __ror__(self: _typeshed.Self, value: Any, /) -> types.UnionType | _typeshed.Self: ... + # `int | str` produces an instance of `UnionType`, but `int | int` produces an instance of `type`, + # and `abc.ABC | abc.ABC` produces an instance of `abc.ABCMeta`. + def __or__(self: _typeshed.Self, value: Any, /) -> types.UnionType | _typeshed.Self: ... + def __ror__(self: _typeshed.Self, value: Any, /) -> types.UnionType | _typeshed.Self: ... if sys.version_info >= (3, 12): __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] __annotations__: dict[str, AnnotationForm] @@ -256,6 +255,7 @@ class int: def __new__(cls, x: ConvertibleToInt = 0, /) -> Self: ... @overload def __new__(cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self: ... + def as_integer_ratio(self) -> tuple[int, Literal[1]]: ... @property def real(self) -> int: ... @@ -267,8 +267,7 @@ class int: def denominator(self) -> Literal[1]: ... def conjugate(self) -> int: ... def bit_length(self) -> int: ... - if sys.version_info >= (3, 10): - def bit_count(self) -> int: ... + def bit_count(self) -> int: ... if sys.version_info >= (3, 11): def to_bytes( @@ -310,6 +309,7 @@ class int: def __rtruediv__(self, value: int, /) -> float: ... def __rmod__(self, value: int, /) -> int: ... def __rdivmod__(self, value: int, /) -> tuple[int, int]: ... + @overload def __pow__(self, x: Literal[0], /) -> Literal[1]: ... @overload @@ -324,6 +324,7 @@ class int: def __pow__(self, value: int, mod: None = None, /) -> Any: ... @overload def __pow__(self, value: int, mod: int, /) -> int: ... + def __rpow__(self, value: int, mod: int | None = None, /) -> Any: ... def __and__(self, value: int, /) -> int: ... def __or__(self, value: int, /) -> int: ... @@ -381,12 +382,14 @@ class float: def __truediv__(self, value: float, /) -> float: ... def __mod__(self, value: float, /) -> float: ... def __divmod__(self, value: float, /) -> tuple[float, float]: ... + @overload def __pow__(self, value: int, mod: None = None, /) -> float: ... # positive __value -> float; negative __value -> complex # return type must be Any as `float | complex` causes too many false-positive errors @overload def __pow__(self, value: float, mod: None = None, /) -> Any: ... + def __radd__(self, value: float, /) -> float: ... def __rsub__(self, value: float, /) -> float: ... def __rmul__(self, value: float, /) -> float: ... @@ -394,6 +397,7 @@ class float: def __rtruediv__(self, value: float, /) -> float: ... def __rmod__(self, value: float, /) -> float: ... def __rdivmod__(self, value: float, /) -> tuple[float, float]: ... + @overload def __rpow__(self, value: _PositiveInteger, mod: None = None, /) -> float: ... @overload @@ -401,14 +405,17 @@ class float: # Returning `complex` for the general case gives too many false-positive errors. @overload def __rpow__(self, value: float, mod: None = None, /) -> Any: ... + def __getnewargs__(self) -> tuple[float]: ... def __trunc__(self) -> int: ... def __ceil__(self) -> int: ... def __floor__(self) -> int: ... + @overload def __round__(self, ndigits: None = None, /) -> int: ... @overload def __round__(self, ndigits: SupportsIndex, /) -> float: ... + def __eq__(self, value: object, /) -> bool: ... def __ne__(self, value: object, /) -> bool: ... def __lt__(self, value: float, /) -> bool: ... @@ -438,6 +445,7 @@ class complex: ) -> Self: ... @overload def __new__(cls, real: str | SupportsComplex | SupportsFloat | SupportsIndex | complex) -> Self: ... + @property def real(self) -> float: ... @property @@ -484,14 +492,17 @@ class str(Sequence[str]): def capitalize(self) -> str: ... # type: ignore[misc] def casefold(self) -> str: ... # type: ignore[misc] def center(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] + def count(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: ... def endswith( self, suffix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, / ) -> bool: ... def expandtabs(self, tabsize: SupportsIndex = 8) -> str: ... # type: ignore[misc] + def find(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def format(self, *args: object, **kwargs: object) -> str: ... + def format_map(self, mapping: _FormatMapMapping, /) -> str: ... def index(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def isalnum(self) -> bool: ... @@ -511,6 +522,7 @@ class str(Sequence[str]): def lower(self) -> str: ... # type: ignore[misc] def lstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] def partition(self, sep: str, /) -> tuple[str, str, str]: ... # type: ignore[misc] + if sys.version_info >= (3, 13): def replace(self, old: str, new: str, /, count: SupportsIndex = -1) -> str: ... # type: ignore[misc] else: @@ -518,6 +530,7 @@ class str(Sequence[str]): def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc] def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc] + def rfind(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def rindex(self, sub: str, start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> int: ... def rjust(self, width: SupportsIndex, fillchar: str = " ", /) -> str: ... # type: ignore[misc] @@ -526,18 +539,37 @@ class str(Sequence[str]): def rstrip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] def split(self, sep: str | None = None, maxsplit: SupportsIndex = -1) -> list[str]: ... # type: ignore[misc] def splitlines(self, keepends: bool = False) -> list[str]: ... # type: ignore[misc] + def startswith( self, prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, / ) -> bool: ... def strip(self, chars: str | None = None, /) -> str: ... # type: ignore[misc] def swapcase(self) -> str: ... # type: ignore[misc] def title(self) -> str: ... # type: ignore[misc] + def translate(self, table: _TranslateTable, /) -> str: ... def upper(self) -> str: ... # type: ignore[misc] def zfill(self, width: SupportsIndex, /) -> str: ... # type: ignore[misc] - @staticmethod - @overload - def maketrans(x: dict[int, _T] | dict[str, _T] | dict[str | int, _T], /) -> dict[int, _T]: ... + + if sys.version_info >= (3, 15): + @staticmethod + @overload + def maketrans( + x: ( + dict[int, _T] + | dict[str, _T] + | dict[str | int, _T] + | frozendict[int, _T] + | frozendict[str, _T] + | frozendict[str | int, _T] + ), + /, + ) -> dict[int, _T]: ... + else: + @staticmethod + @overload + def maketrans(x: dict[int, _T] | dict[str, _T] | dict[str | int, _T], /) -> dict[int, _T]: ... + @staticmethod @overload def maketrans(x: str, y: str, /) -> dict[int, int]: ... @@ -545,21 +577,26 @@ class str(Sequence[str]): @overload def maketrans(x: str, y: str, z: str, /) -> dict[int, int | None]: ... def __add__(self, value: str, /) -> str: ... # type: ignore[misc] + # Incompatible with Sequence.__contains__ def __contains__(self, key: str, /) -> bool: ... # type: ignore[override] def __eq__(self, value: object, /) -> bool: ... def __ge__(self, value: str, /) -> bool: ... def __getitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> str: ... # type: ignore[misc] + def __gt__(self, value: str, /) -> bool: ... def __hash__(self) -> int: ... def __iter__(self) -> Iterator[str]: ... # type: ignore[misc] + def __le__(self, value: str, /) -> bool: ... def __len__(self) -> int: ... def __lt__(self, value: str, /) -> bool: ... def __mod__(self, value: Any, /) -> str: ... def __mul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __ne__(self, value: object, /) -> bool: ... def __rmul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] + def __getnewargs__(self) -> tuple[str]: ... def __format__(self, format_spec: str, /) -> str: ... @@ -571,6 +608,7 @@ class bytes(Sequence[int]): def __new__(cls, string: str, /, encoding: str, errors: str = "strict") -> Self: ... @overload def __new__(cls) -> Self: ... + def capitalize(self) -> bytes: ... def center(self, width: SupportsIndex, fillchar: bytes = b" ", /) -> bytes: ... def count( @@ -605,7 +643,11 @@ class bytes(Sequence[int]): def lower(self) -> bytes: ... def lstrip(self, bytes: ReadableBuffer | None = None, /) -> bytes: ... def partition(self, sep: ReadableBuffer, /) -> tuple[bytes, bytes, bytes]: ... - def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytes: ... + if sys.version_info >= (3, 15): + def replace(self, old: ReadableBuffer, new: ReadableBuffer, /, count: SupportsIndex = -1) -> bytes: ... + else: + def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytes: ... + def removeprefix(self, prefix: ReadableBuffer, /) -> bytes: ... def removesuffix(self, suffix: ReadableBuffer, /) -> bytes: ... def rfind( @@ -633,6 +675,7 @@ class bytes(Sequence[int]): def translate(self, table: ReadableBuffer | None, /, delete: ReadableBuffer = b"") -> bytes: ... def upper(self) -> bytes: ... def zfill(self, width: SupportsIndex, /) -> bytes: ... + if sys.version_info >= (3, 14): @classmethod def fromhex(cls, string: str | ReadableBuffer, /) -> Self: ... @@ -645,10 +688,12 @@ class bytes(Sequence[int]): def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... def __hash__(self) -> int: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> bytes: ... + def __add__(self, value: ReadableBuffer, /) -> bytes: ... def __mul__(self, value: SupportsIndex, /) -> bytes: ... def __rmul__(self, value: SupportsIndex, /) -> bytes: ... @@ -675,6 +720,7 @@ class bytearray(MutableSequence[int]): def __init__(self, ints: Iterable[SupportsIndex] | SupportsIndex | ReadableBuffer, /) -> None: ... @overload def __init__(self, string: str, /, encoding: str, errors: str = "strict") -> None: ... + def append(self, item: SupportsIndex, /) -> None: ... def capitalize(self) -> bytearray: ... def center(self, width: SupportsIndex, fillchar: bytes = b" ", /) -> bytearray: ... @@ -717,7 +763,11 @@ class bytearray(MutableSequence[int]): def remove(self, value: int, /) -> None: ... def removeprefix(self, prefix: ReadableBuffer, /) -> bytearray: ... def removesuffix(self, suffix: ReadableBuffer, /) -> bytearray: ... - def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytearray: ... + if sys.version_info >= (3, 15): + def replace(self, old: ReadableBuffer, new: ReadableBuffer, /, count: SupportsIndex = -1) -> bytearray: ... + else: + def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytearray: ... + def rfind( self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = None, end: SupportsIndex | None = None, / ) -> int: ... @@ -741,8 +791,12 @@ class bytearray(MutableSequence[int]): def swapcase(self) -> bytearray: ... def title(self) -> bytearray: ... def translate(self, table: ReadableBuffer | None, /, delete: bytes = b"") -> bytearray: ... + if sys.version_info >= (3, 15): + def take_bytes(self, n: int | None = None, /) -> bytes: ... + def upper(self) -> bytearray: ... def zfill(self, width: SupportsIndex, /) -> bytearray: ... + if sys.version_info >= (3, 14): @classmethod def fromhex(cls, string: str | ReadableBuffer, /) -> Self: ... @@ -755,14 +809,17 @@ class bytearray(MutableSequence[int]): def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... __hash__: ClassVar[None] # type: ignore[assignment] + @overload def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> bytearray: ... + @overload def __setitem__(self, key: SupportsIndex, value: SupportsIndex, /) -> None: ... @overload def __setitem__(self, key: slice[SupportsIndex | None], value: Iterable[SupportsIndex] | bytes, /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> None: ... def __add__(self, value: ReadableBuffer, /) -> bytearray: ... # The superclass wants us to accept Iterable[int], but that fails at runtime. @@ -824,6 +881,7 @@ class memoryview(Sequence[_I]): exc_tb: TracebackType | None, /, ) -> None: ... + @overload def cast(self, format: Literal["c", "@c"], shape: list[int] | tuple[int, ...] = ...) -> memoryview[bytes]: ... @overload @@ -832,24 +890,24 @@ class memoryview(Sequence[_I]): def cast(self, format: Literal["?"], shape: list[int] | tuple[int, ...] = ...) -> memoryview[bool]: ... @overload def cast(self, format: _IntegerFormats, shape: list[int] | tuple[int, ...] = ...) -> memoryview: ... + @overload def __getitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], /) -> _I: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> memoryview[_I]: ... + def __contains__(self, x: object, /) -> bool: ... def __iter__(self) -> Iterator[_I]: ... def __len__(self) -> int: ... def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... + @overload def __setitem__(self, key: slice[SupportsIndex | None], value: ReadableBuffer, /) -> None: ... @overload def __setitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], value: _I, /) -> None: ... - if sys.version_info >= (3, 10): - def tobytes(self, order: Literal["C", "F", "A"] | None = "C") -> bytes: ... - else: - def tobytes(self, order: Literal["C", "F", "A"] | None = None) -> bytes: ... + def tobytes(self, order: Literal["C", "F", "A"] | None = "C") -> bytes: ... def tolist(self) -> list[int]: ... def toreadonly(self) -> memoryview: ... def release(self) -> None: ... @@ -871,32 +929,39 @@ class memoryview(Sequence[_I]): @final class bool(int): def __new__(cls, o: object = False, /) -> Self: ... + # The following overloads could be represented more elegantly with a TypeVar("_B", bool, int), # however mypy has a bug regarding TypeVar constraints (https://github.com/python/mypy/issues/11880). @overload def __and__(self, value: bool, /) -> bool: ... @overload def __and__(self, value: int, /) -> int: ... + @overload def __or__(self, value: bool, /) -> bool: ... @overload def __or__(self, value: int, /) -> int: ... + @overload def __xor__(self, value: bool, /) -> bool: ... @overload def __xor__(self, value: int, /) -> int: ... + @overload def __rand__(self, value: bool, /) -> bool: ... @overload def __rand__(self, value: int, /) -> int: ... + @overload def __ror__(self, value: bool, /) -> bool: ... @overload def __ror__(self, value: int, /) -> int: ... + @overload def __rxor__(self, value: bool, /) -> bool: ... @overload def __rxor__(self, value: int, /) -> int: ... + def __getnewargs__(self) -> tuple[int]: ... @deprecated("Will throw an error in Python 3.16. Use `not` for logical negation of bools instead.") def __invert__(self) -> int: ... @@ -909,6 +974,7 @@ class slice(Generic[_StartT_co, _StopT_co, _StepT_co]): def step(self) -> _StepT_co: ... @property def stop(self) -> _StopT_co: ... + # Note: __new__ overloads map `None` to `Any`, since users expect slice(x, None) # to be compatible with slice(None, x). # generic slice -------------------------------------------------------------------- @@ -933,6 +999,7 @@ class slice(Generic[_StartT_co, _StopT_co, _StepT_co]): def __new__(cls, start: None, stop: _T2, step: _T3, /) -> slice[Any, _T2, _T3]: ... @overload def __new__(cls, start: _T1, stop: _T2, step: _T3, /) -> slice[_T1, _T2, _T3]: ... + def __eq__(self, value: object, /) -> bool: ... if sys.version_info >= (3, 12): def __hash__(self) -> int: ... @@ -940,16 +1007,20 @@ class slice(Generic[_StartT_co, _StopT_co, _StepT_co]): __hash__: ClassVar[None] # type: ignore[assignment] def indices(self, len: SupportsIndex, /) -> tuple[int, int, int]: ... + if sys.version_info >= (3, 15): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @disjoint_base class tuple(Sequence[_T_co]): def __new__(cls, iterable: Iterable[_T_co] = (), /) -> Self: ... def __len__(self) -> int: ... def __contains__(self, key: object, /) -> bool: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> _T_co: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> tuple[_T_co, ...]: ... + def __iter__(self) -> Iterator[_T_co]: ... def __lt__(self, value: tuple[_T_co, ...], /) -> bool: ... def __le__(self, value: tuple[_T_co, ...], /) -> bool: ... @@ -957,10 +1028,12 @@ class tuple(Sequence[_T_co]): def __ge__(self, value: tuple[_T_co, ...], /) -> bool: ... def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... + @overload def __add__(self, value: tuple[_T_co, ...], /) -> tuple[_T_co, ...]: ... @overload def __add__(self, value: tuple[_T, ...], /) -> tuple[_T_co | _T, ...]: ... + def __mul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ... def __rmul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ... def count(self, value: Any, /) -> int: ... @@ -988,9 +1061,8 @@ class function: if sys.version_info >= (3, 14): __annotate__: AnnotateFunc | None __kwdefaults__: dict[str, Any] | None - if sys.version_info >= (3, 10): - @property - def __builtins__(self) -> dict[str, Any]: ... + @property + def __builtins__(self) -> dict[str, Any]: ... if sys.version_info >= (3, 12): __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] @@ -1024,6 +1096,7 @@ class list(MutableSequence[_T]): def __init__(self) -> None: ... @overload def __init__(self, iterable: Iterable[_T], /) -> None: ... + def copy(self) -> list[_T]: ... def append(self, object: _T, /) -> None: ... def extend(self, iterable: Iterable[_T], /) -> None: ... @@ -1034,6 +1107,7 @@ class list(MutableSequence[_T]): def count(self, value: _T, /) -> int: ... def insert(self, index: SupportsIndex, object: _T, /) -> None: ... def remove(self, value: _T, /) -> None: ... + # Signature of `list.sort` should be kept inline with `collections.UserList.sort()` # and multiprocessing.managers.ListProxy.sort() # @@ -1043,23 +1117,29 @@ class list(MutableSequence[_T]): def sort(self: list[SupportsRichComparisonT], *, key: None = None, reverse: bool = False) -> None: ... @overload def sort(self, *, key: Callable[[_T], SupportsRichComparison], reverse: bool = False) -> None: ... + def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T]: ... __hash__: ClassVar[None] # type: ignore[assignment] + @overload def __getitem__(self, i: SupportsIndex, /) -> _T: ... @overload def __getitem__(self, s: slice[SupportsIndex | None], /) -> list[_T]: ... + @overload def __setitem__(self, key: SupportsIndex, value: _T, /) -> None: ... @overload def __setitem__(self, key: slice[SupportsIndex | None], value: Iterable[_T], /) -> None: ... + def __delitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> None: ... + # Overloading looks unnecessary, but is needed to work around complex mypy problems @overload def __add__(self, value: list[_T], /) -> list[_T]: ... @overload def __add__(self, value: list[_S], /) -> list[_S | _T]: ... + def __iadd__(self, value: Iterable[_T], /) -> Self: ... # type: ignore[misc] def __mul__(self, value: SupportsIndex, /) -> list[_T]: ... def __rmul__(self, value: SupportsIndex, /) -> list[_T]: ... @@ -1105,11 +1185,13 @@ class dict(MutableMapping[_KT, _VT]): def __init__(self: dict[str, str], iterable: Iterable[list[str]], /) -> None: ... @overload def __init__(self: dict[bytes, bytes], iterable: Iterable[list[bytes]], /) -> None: ... + def __new__(cls, /, *args: Any, **kwargs: Any) -> Self: ... def copy(self) -> dict[_KT, _VT]: ... def keys(self) -> dict_keys[_KT, _VT]: ... def values(self) -> dict_values[_KT, _VT]: ... def items(self) -> dict_items[_KT, _VT]: ... + # Signature of `dict.fromkeys` should be kept identical to # `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections` # TODO: the true signature of `dict.fromkeys` is not expressible in the current type system. @@ -1120,6 +1202,7 @@ class dict(MutableMapping[_KT, _VT]): @classmethod @overload def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> dict[_T, _S]: ... + # Positional-only in dict, but not in MutableMapping @overload # type: ignore[override] def get(self, key: _KT, default: None = None, /) -> _VT | None: ... @@ -1127,12 +1210,14 @@ class dict(MutableMapping[_KT, _VT]): def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + @overload def pop(self, key: _KT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _VT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def __len__(self) -> int: ... def __getitem__(self, key: _KT, /) -> _VT: ... def __setitem__(self, key: _KT, value: _VT, /) -> None: ... @@ -1142,26 +1227,102 @@ class dict(MutableMapping[_KT, _VT]): def __reversed__(self) -> Iterator[_KT]: ... __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - @overload - def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... - @overload - def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... - @overload - def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... - @overload - def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + if sys.version_info >= (3, 15): + @overload + def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... + @overload + def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + else: + @overload + def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + # dict.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, value: SupportsKeysAndGetItem[_KT, _VT], /) -> Self: ... @overload def __ior__(self, value: Iterable[tuple[_KT, _VT]], /) -> Self: ... +if sys.version_info >= (3, 15): + @disjoint_base + class frozendict(Mapping[_KT, _VT]): + @overload + def __new__(cls, /) -> frozendict[Any, Any]: ... + @overload + def __new__(cls: type[frozendict[str, _VT]], /, **kwargs: _VT) -> frozendict[str, _VT]: ... + @overload + def __new__(cls, map: SupportsKeysAndGetItem[_KT, _VT], /) -> frozendict[_KT, _VT]: ... + @overload + def __new__( + cls: type[frozendict[str, _VT]], map: SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT + ) -> frozendict[str, _VT]: ... + @overload + def __new__(cls, iterable: Iterable[tuple[_KT, _VT]], /) -> frozendict[_KT, _VT]: ... + @overload + def __new__( + cls: type[frozendict[str, _VT]], iterable: Iterable[tuple[str, _VT]], /, **kwargs: _VT + ) -> frozendict[str, _VT]: ... + + def copy(self) -> frozendict[_KT, _VT]: ... + + @overload + @classmethod + def fromkeys(cls, iterable: Iterable[_T], value: None = None, /) -> frozendict[_T, Any | None]: ... + @overload + @classmethod + def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> frozendict[_T, _S]: ... + + @overload # type: ignore[override] + def get(self, key: _KT, default: None = None, /) -> _VT | None: ... + @overload + def get(self, key: _KT, default: _VT, /) -> _VT: ... + @overload + def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + + def keys(self) -> dict_keys[_KT, _VT]: ... + def values(self) -> dict_values[_KT, _VT]: ... + def items(self) -> dict_items[_KT, _VT]: ... + def __len__(self) -> int: ... + def __getitem__(self, key: _KT, /) -> _VT: ... + def __reversed__(self) -> Iterator[_KT]: ... + def __iter__(self) -> Iterator[_KT]: ... + def __hash__(self) -> int: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + @overload + def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... + @overload + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: frozendict[_KT, _VT], /) -> frozendict[_KT, _VT]: ... + @overload + def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + @disjoint_base class set(MutableSet[_T]): @overload def __init__(self) -> None: ... @overload def __init__(self, iterable: Iterable[_T], /) -> None: ... + def add(self, element: _T, /) -> None: ... def copy(self) -> set[_T]: ... def difference(self, *s: Iterable[object]) -> set[_T]: ... @@ -1202,6 +1363,7 @@ class frozenset(AbstractSet[_T_co]): def __new__(cls) -> Self: ... @overload def __new__(cls, iterable: Iterable[_T_co], /) -> Self: ... + def copy(self) -> frozenset[_T_co]: ... def difference(self, *s: Iterable[object]) -> frozenset[_T_co]: ... def intersection(self, *s: Iterable[object]) -> frozenset[_T_co]: ... @@ -1240,10 +1402,12 @@ class range(Sequence[int]): def stop(self) -> int: ... @property def step(self) -> int: ... + @overload def __new__(cls, stop: SupportsIndex, /) -> Self: ... @overload def __new__(cls, start: SupportsIndex, stop: SupportsIndex, step: SupportsIndex = 1, /) -> Self: ... + def count(self, value: int, /) -> int: ... def index(self, value: int, /) -> int: ... # type: ignore[override] def __len__(self) -> int: ... @@ -1251,10 +1415,12 @@ class range(Sequence[int]): def __hash__(self) -> int: ... def __contains__(self, key: object, /) -> bool: ... def __iter__(self) -> Iterator[int]: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> range: ... + def __reversed__(self) -> Iterator[int]: ... @disjoint_base @@ -1276,10 +1442,12 @@ class property: def getter(self, fget: Callable[[Any], Any], /) -> property: ... def setter(self, fset: Callable[[Any, Any], None], /) -> property: ... def deleter(self, fdel: Callable[[Any], None], /) -> property: ... + @overload def __get__(self, instance: None, owner: type, /) -> Self: ... @overload def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ... + def __set__(self, instance: Any, value: Any, /) -> None: ... def __delete__(self, instance: Any, /) -> None: ... @@ -1287,77 +1455,132 @@ def abs(x: SupportsAbs[_T], /) -> _T: ... def all(iterable: Iterable[object], /) -> bool: ... def any(iterable: Iterable[object], /) -> bool: ... def ascii(obj: object, /) -> str: ... -def bin(number: SupportsIndex, /) -> str: ... + +if sys.version_info >= (3, 15): + def bin(integer: SupportsIndex, /) -> str: ... + +else: + def bin(number: SupportsIndex, /) -> str: ... + def breakpoint(*args: Any, **kws: Any) -> None: ... def callable(obj: object, /) -> TypeIs[Callable[..., object]]: ... def chr(i: SupportsIndex, /) -> str: ... +def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co: ... -if sys.version_info >= (3, 10): - def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co: ... - @type_check_only - class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]): - def __anext__(self) -> _AwaitableT_co: ... +@type_check_only +class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]): + def __anext__(self) -> _AwaitableT_co: ... - @overload - # `anext` is not, in fact, an async function. When default is not provided - # `anext` is just a passthrough for `obj.__anext__` - # See discussion in #7491 and pure-Python implementation of `anext` at https://github.com/python/cpython/blob/ea786a882b9ed4261eafabad6011bc7ef3b5bf94/Lib/test/test_asyncgen.py#L52-L80 - def anext(i: _SupportsSynchronousAnext[_AwaitableT], /) -> _AwaitableT: ... - @overload - async def anext(i: SupportsAnext[_T], default: _VT, /) -> _T | _VT: ... +@overload +# `anext` is not, in fact, an async function. When default is not provided +# `anext` is just a passthrough for `obj.__anext__` +# See discussion in #7491 and pure-Python implementation of `anext` at https://github.com/python/cpython/blob/ea786a882b9ed4261eafabad6011bc7ef3b5bf94/Lib/test/test_asyncgen.py#L52-L80 +def anext(i: _SupportsSynchronousAnext[_AwaitableT], /) -> _AwaitableT: ... +@overload +async def anext(i: SupportsAnext[_T], default: _VT, /) -> _T | _VT: ... # compile() returns a CodeType, unless the flags argument includes PyCF_ONLY_AST (=1024), # in which case it returns ast.AST. We have overloads for flag 0 (the default) and for # explicitly passing PyCF_ONLY_AST. We fall back to Any for other values of flags. -@overload -def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | bytes | PathLike[Any], - mode: str, - flags: Literal[0], - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, -) -> CodeType: ... -@overload -def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | bytes | PathLike[Any], - mode: str, - *, - dont_inherit: bool = False, - optimize: int = -1, - _feature_version: int = -1, -) -> CodeType: ... -@overload -def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | bytes | PathLike[Any], - mode: str, - flags: Literal[1024], - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, -) -> _ast.AST: ... -@overload -def compile( - source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, - filename: str | bytes | PathLike[Any], - mode: str, - flags: int, - dont_inherit: bool = False, - optimize: int = -1, - *, - _feature_version: int = -1, -) -> Any: ... +if sys.version_info >= (3, 15): + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: Literal[0], + dont_inherit: bool = False, + optimize: int = -1, + *, + module: str | None = None, + _feature_version: int = -1, + ) -> CodeType: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + *, + dont_inherit: bool = False, + optimize: int = -1, + module: str | None = None, + _feature_version: int = -1, + ) -> CodeType: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: Literal[1024], + dont_inherit: bool = False, + optimize: int = -1, + *, + module: str | None = None, + _feature_version: int = -1, + ) -> _ast.AST: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: int, + dont_inherit: bool = False, + optimize: int = -1, + *, + module: str | None = None, + _feature_version: int = -1, + ) -> Any: ... +else: + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: Literal[0], + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, + ) -> CodeType: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + *, + dont_inherit: bool = False, + optimize: int = -1, + _feature_version: int = -1, + ) -> CodeType: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: Literal[1024], + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, + ) -> _ast.AST: ... + @overload + def compile( + source: str | ReadableBuffer | _ast.Module | _ast.Expression | _ast.Interactive, + filename: str | bytes | PathLike[Any], + mode: str, + flags: int, + dont_inherit: bool = False, + optimize: int = -1, + *, + _feature_version: int = -1, + ) -> Any: ... copyright: _sitebuiltins._Printer credits: _sitebuiltins._Printer def delattr(obj: object, name: str, /) -> None: ... def dir(o: object = ..., /) -> list[str]: ... + @overload def divmod(x: SupportsDivMod[_T_contra, _T_co], y: _T_contra, /) -> _T_co: ... @overload @@ -1365,7 +1588,15 @@ def divmod(x: _T_contra, y: SupportsRDivMod[_T_contra, _T_co], /) -> _T_co: ... # The `globals` argument to `eval` has to be `dict[str, Any]` rather than `dict[str, object]` due to invariance. # (The `globals` argument has to be a "real dict", rather than any old mapping, unlike the `locals` argument.) -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 15): + def eval( + source: str | ReadableBuffer | CodeType, + /, + globals: dict[str, Any] | frozendict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + ) -> Any: ... + +elif sys.version_info >= (3, 13): def eval( source: str | ReadableBuffer | CodeType, /, @@ -1382,7 +1613,17 @@ else: ) -> Any: ... # Comment above regarding `eval` applies to `exec` as well -if sys.version_info >= (3, 13): +if sys.version_info >= (3, 15): + def exec( + source: str | ReadableBuffer | CodeType, + /, + globals: dict[str, Any] | frozendict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + *, + closure: tuple[CellType, ...] | None = None, + ) -> None: ... + +elif sys.version_info >= (3, 13): def exec( source: str | ReadableBuffer | CodeType, /, @@ -1422,10 +1663,12 @@ class filter(Iterator[_T]): def __new__(cls, function: Callable[[_S], TypeIs[_T]], iterable: Iterable[_S], /) -> Self: ... @overload def __new__(cls, function: Callable[[_T], Any], iterable: Iterable[_T], /) -> Self: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T: ... def format(value: object, format_spec: str = "", /) -> str: ... + @overload def getattr(o: object, name: str, /) -> Any: ... @@ -1442,15 +1685,22 @@ def getattr(o: object, name: str, default: list[Any], /) -> Any | list[Any]: ... def getattr(o: object, name: str, default: dict[Any, Any], /) -> Any | dict[Any, Any]: ... @overload def getattr(o: object, name: str, default: _T, /) -> Any | _T: ... + def globals() -> dict[str, Any]: ... def hasattr(obj: object, name: str, /) -> bool: ... def hash(obj: object, /) -> int: ... help: _sitebuiltins._Helper -def hex(number: SupportsIndex, /) -> str: ... +if sys.version_info >= (3, 15): + def hex(integer: SupportsIndex, /) -> str: ... + +else: + def hex(number: SupportsIndex, /) -> str: ... + def id(obj: object, /) -> int: ... def input(prompt: object = "", /) -> str: ... + @type_check_only class _GetItemIterable(Protocol[_T_co]): def __getitem__(self, i: int, /) -> _T_co: ... @@ -1464,10 +1714,7 @@ def iter(object: Callable[[], _T | None], sentinel: None, /) -> Iterator[_T]: .. @overload def iter(object: Callable[[], _T], sentinel: object, /) -> Iterator[_T]: ... -if sys.version_info >= (3, 10): - _ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...] -else: - _ClassInfo: TypeAlias = type | tuple[_ClassInfo, ...] +_ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...] def isinstance(obj: object, class_or_tuple: _ClassInfo, /) -> bool: ... def issubclass(cls: type, class_or_tuple: _ClassInfo, /) -> bool: ... @@ -1476,6 +1723,7 @@ def len(obj: Sized, /) -> int: ... license: _sitebuiltins._Printer def locals() -> dict[str, Any]: ... + @disjoint_base class map(Iterator[_S]): # 3.14 adds `strict` argument. @@ -1597,6 +1845,7 @@ def max(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison def max(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... @overload def max(iterable: Iterable[_T1], /, *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... + @overload def min( arg1: SupportsRichComparisonT, arg2: SupportsRichComparisonT, /, *_args: SupportsRichComparisonT, key: None = None @@ -1611,11 +1860,17 @@ def min(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison def min(iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T: ... @overload def min(iterable: Iterable[_T1], /, *, key: Callable[[_T1], SupportsRichComparison], default: _T2) -> _T1 | _T2: ... + @overload def next(i: SupportsNext[_T], /) -> _T: ... @overload def next(i: SupportsNext[_T], default: _VT, /) -> _T | _VT: ... -def oct(number: SupportsIndex, /) -> str: ... + +if sys.version_info >= (3, 15): + def oct(integer: SupportsIndex, /) -> str: ... + +else: + def oct(number: SupportsIndex, /) -> str: ... _Opener: TypeAlias = Callable[[str, int], int] @@ -1705,7 +1960,9 @@ def open( closefd: bool = True, opener: _Opener | None = None, ) -> IO[Any]: ... + def ord(c: str | bytes | bytearray, /) -> int: ... + @type_check_only class _SupportsWriteAndFlush(SupportsWrite[_T_contra], SupportsFlush, Protocol[_T_contra]): ... @@ -1789,6 +2046,7 @@ class reversed(Iterator[_T]): def __new__(cls, sequence: Reversible[_T], /) -> Iterator[_T]: ... # type: ignore[misc] @overload def __new__(cls, sequence: SupportsLenAndGetItem[_T], /) -> Iterator[_T]: ... # type: ignore[misc] + def __iter__(self) -> Self: ... def __next__(self) -> _T: ... def __length_hint__(self) -> int: ... @@ -1815,6 +2073,20 @@ def round(number: _SupportsRound2[_T], ndigits: SupportsIndex) -> _T: ... # See https://github.com/python/typeshed/pull/6292#discussion_r748875189 # for why arg 3 of `setattr` should be annotated with `Any` and not `object` def setattr(obj: object, name: str, value: Any, /) -> None: ... + +if sys.version_info >= (3, 15): + @final + class sentinel: + __name__: str + __module__: str + def __new__(cls, name: str, /, *, repr: str | None = None) -> sentinel: ... + def __copy__(self, /) -> sentinel: ... + def __deepcopy__(self, memo: Any, /) -> sentinel: ... + # `other` can be any legal form for unions. + # `x | x` creates a `sentinel` instance if `x` is a sentinel, not a `UnionType` instance. + def __or__(self, other: Any, /) -> UnionType | sentinel: ... + def __ror__(self, other: Any, /) -> UnionType | sentinel: ... + @overload def sorted( iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, reverse: bool = False @@ -1847,84 +2119,48 @@ def sum(iterable: Iterable[_AddableT1], /, start: _AddableT2) -> _AddableT1 | _A def vars(object: type, /) -> types.MappingProxyType[str, Any]: ... @overload def vars(object: Any = ..., /) -> dict[str, Any]: ... + @disjoint_base class zip(Iterator[_T_co]): - if sys.version_info >= (3, 10): - @overload - def __new__(cls, *, strict: bool = False) -> zip[Any]: ... - @overload - def __new__(cls, iter1: Iterable[_T1], /, *, strict: bool = False) -> zip[tuple[_T1]]: ... - @overload - def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /, *, strict: bool = False) -> zip[tuple[_T1, _T2]]: ... - @overload - def __new__( - cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /, *, strict: bool = False - ) -> zip[tuple[_T1, _T2, _T3]]: ... - @overload - def __new__( - cls, - iter1: Iterable[_T1], - iter2: Iterable[_T2], - iter3: Iterable[_T3], - iter4: Iterable[_T4], - /, - *, - strict: bool = False, - ) -> zip[tuple[_T1, _T2, _T3, _T4]]: ... - @overload - def __new__( - cls, - iter1: Iterable[_T1], - iter2: Iterable[_T2], - iter3: Iterable[_T3], - iter4: Iterable[_T4], - iter5: Iterable[_T5], - /, - *, - strict: bool = False, - ) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]: ... - @overload - def __new__( - cls, - iter1: Iterable[Any], - iter2: Iterable[Any], - iter3: Iterable[Any], - iter4: Iterable[Any], - iter5: Iterable[Any], - iter6: Iterable[Any], - /, - *iterables: Iterable[Any], - strict: bool = False, - ) -> zip[tuple[Any, ...]]: ... - else: - @overload - def __new__(cls) -> zip[Any]: ... - @overload - def __new__(cls, iter1: Iterable[_T1], /) -> zip[tuple[_T1]]: ... - @overload - def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /) -> zip[tuple[_T1, _T2]]: ... - @overload - def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /) -> zip[tuple[_T1, _T2, _T3]]: ... - @overload - def __new__( - cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], / - ) -> zip[tuple[_T1, _T2, _T3, _T4]]: ... - @overload - def __new__( - cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], / - ) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]: ... - @overload - def __new__( - cls, - iter1: Iterable[Any], - iter2: Iterable[Any], - iter3: Iterable[Any], - iter4: Iterable[Any], - iter5: Iterable[Any], - iter6: Iterable[Any], - /, - *iterables: Iterable[Any], - ) -> zip[tuple[Any, ...]]: ... + @overload + def __new__(cls, *, strict: bool = False) -> zip[Any]: ... + @overload + def __new__(cls, iter1: Iterable[_T1], /, *, strict: bool = False) -> zip[tuple[_T1]]: ... + @overload + def __new__(cls, iter1: Iterable[_T1], iter2: Iterable[_T2], /, *, strict: bool = False) -> zip[tuple[_T1, _T2]]: ... + @overload + def __new__( + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /, *, strict: bool = False + ) -> zip[tuple[_T1, _T2, _T3]]: ... + @overload + def __new__( + cls, iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /, *, strict: bool = False + ) -> zip[tuple[_T1, _T2, _T3, _T4]]: ... + @overload + def __new__( + cls, + iter1: Iterable[_T1], + iter2: Iterable[_T2], + iter3: Iterable[_T3], + iter4: Iterable[_T4], + iter5: Iterable[_T5], + /, + *, + strict: bool = False, + ) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]: ... + @overload + def __new__( + cls, + iter1: Iterable[Any], + iter2: Iterable[Any], + iter3: Iterable[Any], + iter4: Iterable[Any], + iter5: Iterable[Any], + iter6: Iterable[Any], + /, + *iterables: Iterable[Any], + strict: bool = False, + ) -> zip[tuple[Any, ...]]: ... def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... @@ -1938,31 +2174,24 @@ def __import__( fromlist: Sequence[str] | None = (), level: int = 0, ) -> types.ModuleType: ... -def __build_class__(func: Callable[[], CellType | Any], name: str, /, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ... -if sys.version_info >= (3, 10): - from types import EllipsisType, NotImplementedType - - # Backwards compatibility hack for folks who relied on the ellipsis type - # existing in typeshed in Python 3.9 and earlier. - ellipsis = EllipsisType - - Ellipsis: EllipsisType - NotImplemented: NotImplementedType -else: - # Actually the type of Ellipsis is , but since it's - # not exposed anywhere under that name, we make it private here. - @final - @type_check_only - class ellipsis: ... +if sys.version_info >= (3, 15): + def __lazy_import__( + name: str, + globals: Mapping[str, object] | None = None, + locals: Mapping[str, object] | None = None, + fromlist: Sequence[str] | None = (), + level: int = 0, + ) -> Any: ... - Ellipsis: ellipsis +def __build_class__(func: Callable[[], CellType | Any], name: str, /, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ... - @final - @type_check_only - class _NotImplementedType(Any): ... +# Backwards compatibility hack for folks who relied on the ellipsis type +# existing in typeshed in Python 3.9 and earlier. +ellipsis = EllipsisType - NotImplemented: _NotImplementedType +Ellipsis: EllipsisType +NotImplemented: NotImplementedType @disjoint_base class BaseException: @@ -2015,15 +2244,11 @@ if sys.platform == "win32": class ArithmeticError(Exception): ... class AssertionError(Exception): ... -if sys.version_info >= (3, 10): - @disjoint_base - class AttributeError(Exception): - def __init__(self, *args: object, name: str | None = None, obj: object = None) -> None: ... - name: str | None - obj: object - -else: - class AttributeError(Exception): ... +@disjoint_base +class AttributeError(Exception): + def __init__(self, *args: object, name: str | None = None, obj: object = None) -> None: ... + name: str | None + obj: object class BufferError(Exception): ... class EOFError(Exception): ... @@ -2037,17 +2262,16 @@ class ImportError(Exception): if sys.version_info >= (3, 12): name_from: str | None # undocumented +if sys.version_info >= (3, 15): + class ImportCycleError(ImportError): ... + class LookupError(Exception): ... class MemoryError(Exception): ... -if sys.version_info >= (3, 10): - @disjoint_base - class NameError(Exception): - def __init__(self, *args: object, name: str | None = None) -> None: ... - name: str | None - -else: - class NameError(Exception): ... +@disjoint_base +class NameError(Exception): + def __init__(self, *args: object, name: str | None = None) -> None: ... + name: str | None class ReferenceError(Exception): ... class RuntimeError(Exception): ... @@ -2063,9 +2287,8 @@ class SyntaxError(Exception): # Errors are displayed differently if this attribute exists on the exception. # The value is always None. print_file_and_line: None - if sys.version_info >= (3, 10): - end_lineno: int | None - end_offset: int | None + end_lineno: int | None + end_offset: int | None @overload def __init__(self) -> None: ... @@ -2074,12 +2297,11 @@ class SyntaxError(Exception): # Second argument is the tuple (filename, lineno, offset, text) @overload def __init__(self, msg: str, info: tuple[str | None, int | None, int | None, str | None], /) -> None: ... - if sys.version_info >= (3, 10): - # end_lineno and end_offset must both be provided if one is. - @overload - def __init__( - self, msg: str, info: tuple[str | None, int | None, int | None, str | None, int | None, int | None], / - ) -> None: ... + # end_lineno and end_offset must both be provided if one is. + @overload + def __init__( + self, msg: str, info: tuple[str | None, int | None, int | None, str | None, int | None, int | None], / + ) -> None: ... # If you provide more than two arguments, it still creates the SyntaxError, but # the arguments from the info tuple are not parsed. This form is omitted. @@ -2155,9 +2377,7 @@ class ImportWarning(Warning): ... class UnicodeWarning(Warning): ... class BytesWarning(Warning): ... class ResourceWarning(Warning): ... - -if sys.version_info >= (3, 10): - class EncodingWarning(Warning): ... +class EncodingWarning(Warning): ... if sys.version_info >= (3, 11): _BaseExceptionT_co = TypeVar("_BaseExceptionT_co", bound=BaseException, covariant=True, default=BaseException) @@ -2174,6 +2394,7 @@ if sys.version_info >= (3, 11): def message(self) -> str: ... @property def exceptions(self) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: ... + @overload def subgroup( self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / @@ -2186,6 +2407,7 @@ if sys.version_info >= (3, 11): def subgroup( self, matcher_value: Callable[[_BaseExceptionT_co | Self], bool], / ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: ... + @overload def split( self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / @@ -2198,11 +2420,13 @@ if sys.version_info >= (3, 11): def split( self, matcher_value: Callable[[_BaseExceptionT_co | Self], bool], / ) -> tuple[BaseExceptionGroup[_BaseExceptionT_co] | None, BaseExceptionGroup[_BaseExceptionT_co] | None]: ... + # In reality it is `NonEmptySequence`: @overload def derive(self, excs: Sequence[_ExceptionT], /) -> ExceptionGroup[_ExceptionT]: ... @overload def derive(self, excs: Sequence[_BaseExceptionT], /) -> BaseExceptionGroup[_BaseExceptionT]: ... + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception): @@ -2210,6 +2434,7 @@ if sys.version_info >= (3, 11): def __init__(self, message: str, exceptions: Sequence[_ExceptionT_co], /) -> None: ... @property def exceptions(self) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: ... + # We accept a narrower type, but that's OK. @overload # type: ignore[override] def subgroup( @@ -2219,6 +2444,7 @@ if sys.version_info >= (3, 11): def subgroup( self, matcher_value: Callable[[_ExceptionT_co | Self], bool], / ) -> ExceptionGroup[_ExceptionT_co] | None: ... + @overload # type: ignore[override] def split( self, matcher_value: type[_ExceptionT] | tuple[type[_ExceptionT], ...], / diff --git a/mypy/typeshed/stdlib/bz2.pyi b/mypy/typeshed/stdlib/bz2.pyi index 7bd829d040cb8..fec6b30af2f5a 100644 --- a/mypy/typeshed/stdlib/bz2.pyi +++ b/mypy/typeshed/stdlib/bz2.pyi @@ -3,8 +3,8 @@ from _bz2 import BZ2Compressor as BZ2Compressor, BZ2Decompressor as BZ2Decompres from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Iterable from io import TextIOWrapper -from typing import IO, Literal, Protocol, SupportsIndex, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import IO, Literal, Protocol, SupportsIndex, TypeAlias, overload, type_check_only +from typing_extensions import Self if sys.version_info >= (3, 14): from compression._common._streams import BaseStream, _Reader @@ -100,6 +100,7 @@ def open( class BZ2File(BaseStream, IO[bytes]): def __enter__(self) -> Self: ... + @overload def __init__(self, filename: _WritableFileobj, mode: _WriteBinaryMode, *, compresslevel: int = 9) -> None: ... @overload @@ -108,6 +109,7 @@ class BZ2File(BaseStream, IO[bytes]): def __init__( self, filename: StrOrBytesPath, mode: _ReadBinaryMode | _WriteBinaryMode = "r", *, compresslevel: int = 9 ) -> None: ... + def read(self, size: int | None = -1) -> bytes: ... def read1(self, size: int = -1) -> bytes: ... def readline(self, size: SupportsIndex = -1) -> bytes: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/cProfile.pyi b/mypy/typeshed/stdlib/cProfile.pyi index e921584d43905..0e414206f5156 100644 --- a/mypy/typeshed/stdlib/cProfile.pyi +++ b/mypy/typeshed/stdlib/cProfile.pyi @@ -1,9 +1,10 @@ import _lsprof +import sys from _typeshed import StrOrBytesPath, Unused from collections.abc import Callable, Mapping from types import CodeType -from typing import Any, TypeVar -from typing_extensions import ParamSpec, Self, TypeAlias +from typing import Any, ParamSpec, TypeAlias, TypeVar +from typing_extensions import Self __all__ = ["run", "runctx", "Profile"] @@ -28,4 +29,5 @@ class Profile(_lsprof.Profiler): def __enter__(self) -> Self: ... def __exit__(self, *exc_info: Unused) -> None: ... -def label(code: str | CodeType) -> _Label: ... # undocumented +if sys.version_info < (3, 15): + def label(code: str | CodeType) -> _Label: ... # undocumented diff --git a/mypy/typeshed/stdlib/calendar.pyi b/mypy/typeshed/stdlib/calendar.pyi index 0d3a0a7490a33..fb75d9559e977 100644 --- a/mypy/typeshed/stdlib/calendar.pyi +++ b/mypy/typeshed/stdlib/calendar.pyi @@ -2,12 +2,18 @@ import datetime import enum import sys from _typeshed import Unused -from collections.abc import Iterable, Sequence +from collections.abc import Iterable, Iterator from time import struct_time -from typing import ClassVar, Final -from typing_extensions import TypeAlias +from typing import ClassVar, Final, TypeAlias, overload __all__ = [ + "FRIDAY", + "MONDAY", + "SATURDAY", + "SUNDAY", + "THURSDAY", + "TUESDAY", + "WEDNESDAY", "IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", @@ -34,8 +40,6 @@ __all__ = [ "weekheader", ] -if sys.version_info >= (3, 10): - __all__ += ["FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"] if sys.version_info >= (3, 12): __all__ += [ "Day", @@ -53,6 +57,8 @@ if sys.version_info >= (3, 12): "NOVEMBER", "DECEMBER", ] +if sys.version_info >= (3, 15): + __all__ += ["standalone_month_name", "standalone_month_abbr"] _LocaleType: TypeAlias = tuple[str | None, str | None] @@ -74,23 +80,23 @@ class Calendar: def __init__(self, firstweekday: int = 0) -> None: ... def getfirstweekday(self) -> int: ... def setfirstweekday(self, firstweekday: int) -> None: ... - def iterweekdays(self) -> Iterable[int]: ... - def itermonthdates(self, year: int, month: int) -> Iterable[datetime.date]: ... - def itermonthdays2(self, year: int, month: int) -> Iterable[tuple[int, int]]: ... - def itermonthdays(self, year: int, month: int) -> Iterable[int]: ... + def iterweekdays(self) -> Iterator[int]: ... + def itermonthdates(self, year: int, month: int) -> Iterator[datetime.date]: ... + def itermonthdays2(self, year: int, month: int) -> Iterator[tuple[int, int]]: ... + def itermonthdays(self, year: int, month: int) -> Iterator[int]: ... def monthdatescalendar(self, year: int, month: int) -> list[list[datetime.date]]: ... def monthdays2calendar(self, year: int, month: int) -> list[list[tuple[int, int]]]: ... def monthdayscalendar(self, year: int, month: int) -> list[list[int]]: ... def yeardatescalendar(self, year: int, width: int = 3) -> list[list[list[list[datetime.date]]]]: ... def yeardays2calendar(self, year: int, width: int = 3) -> list[list[list[list[tuple[int, int]]]]]: ... def yeardayscalendar(self, year: int, width: int = 3) -> list[list[list[list[int]]]]: ... - def itermonthdays3(self, year: int, month: int) -> Iterable[tuple[int, int, int]]: ... - def itermonthdays4(self, year: int, month: int) -> Iterable[tuple[int, int, int, int]]: ... + def itermonthdays3(self, year: int, month: int) -> Iterator[tuple[int, int, int]]: ... + def itermonthdays4(self, year: int, month: int) -> Iterator[tuple[int, int, int, int]]: ... class TextCalendar(Calendar): - def prweek(self, theweek: int, width: int) -> None: ... + def prweek(self, theweek: Iterable[tuple[int, int]], width: int) -> None: ... def formatday(self, day: int, weekday: int, width: int) -> str: ... - def formatweek(self, theweek: int, width: int) -> str: ... + def formatweek(self, theweek: Iterable[tuple[int, int]], width: int) -> str: ... def formatweekday(self, day: int, width: int) -> str: ... def formatweekheader(self, width: int) -> str: ... def formatmonthname(self, theyear: int, themonth: int, width: int, withyear: bool = True) -> str: ... @@ -123,6 +129,11 @@ class HTMLCalendar(Calendar): def formatweekheader(self) -> str: ... def formatmonthname(self, theyear: int, themonth: int, withyear: bool = True) -> str: ... def formatmonth(self, theyear: int, themonth: int, withyear: bool = True) -> str: ... + if sys.version_info >= (3, 15): + def formatmonthpage( + self, theyear: int, themonth: int, width: int = 3, css: str | None = "calendar.css", encoding: str | None = None + ) -> bytes: ... + def formatyear(self, theyear: int, width: int = 3) -> str: ... def formatyearpage( self, theyear: int, width: int = 3, css: str | None = "calendar.css", encoding: str | None = None @@ -145,14 +156,38 @@ c: TextCalendar def setfirstweekday(firstweekday: int) -> None: ... def format(cols: int, colwidth: int = 20, spacing: int = 6) -> str: ... -def formatstring(cols: int, colwidth: int = 20, spacing: int = 6) -> str: ... +def formatstring(cols: Iterable[str], colwidth: int = 20, spacing: int = 6) -> str: ... def timegm(tuple: tuple[int, ...] | struct_time) -> int: ... # Data attributes -day_name: Sequence[str] -day_abbr: Sequence[str] -month_name: Sequence[str] -month_abbr: Sequence[str] +class _localized_month: + format: str + def __init__(self, format: str) -> None: ... + + @overload + def __getitem__(self, i: int) -> str: ... + @overload + def __getitem__(self, i: slice) -> list[str]: ... + + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... + +class _localized_day: + format: str + def __init__(self, format: str) -> None: ... + + @overload + def __getitem__(self, i: int) -> str: ... + @overload + def __getitem__(self, i: slice) -> list[str]: ... + + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... + +day_name: _localized_day +day_abbr: _localized_day +month_name: _localized_month +month_abbr: _localized_month if sys.version_info >= (3, 12): class Month(enum.IntEnum): @@ -208,3 +243,7 @@ else: SUNDAY: Final = 6 EPOCH: Final = 1970 + +if sys.version_info >= (3, 15): + standalone_month_name: _localized_month + standalone_month_abbr: _localized_month diff --git a/mypy/typeshed/stdlib/cgi.pyi b/mypy/typeshed/stdlib/cgi.pyi index 0f9d4343b6307..b7f88ded315f4 100644 --- a/mypy/typeshed/stdlib/cgi.pyi +++ b/mypy/typeshed/stdlib/cgi.pyi @@ -32,6 +32,7 @@ def parse( def parse_multipart( fp: IO[Any], pdict: SupportsGetItem[str, bytes], encoding: str = "utf-8", errors: str = "replace", separator: str = "&" ) -> dict[str, list[Any]]: ... + @type_check_only class _Environ(Protocol): def __getitem__(self, k: str, /) -> str: ... diff --git a/mypy/typeshed/stdlib/cmath.pyi b/mypy/typeshed/stdlib/cmath.pyi index fdf8ae7bfed8d..554eb54e2e4cc 100644 --- a/mypy/typeshed/stdlib/cmath.pyi +++ b/mypy/typeshed/stdlib/cmath.pyi @@ -1,5 +1,4 @@ -from typing import Final, SupportsComplex, SupportsFloat, SupportsIndex -from typing_extensions import TypeAlias +from typing import Final, SupportsComplex, SupportsFloat, SupportsIndex, TypeAlias e: Final[float] pi: Final[float] diff --git a/mypy/typeshed/stdlib/codecs.pyi b/mypy/typeshed/stdlib/codecs.pyi index 9164a4a626d4f..b341d0f813c41 100644 --- a/mypy/typeshed/stdlib/codecs.pyi +++ b/mypy/typeshed/stdlib/codecs.pyi @@ -4,8 +4,8 @@ from _codecs import * from _typeshed import ReadableBuffer from abc import abstractmethod from collections.abc import Callable, Generator, Iterable -from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated, disjoint_base +from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO, TypeAlias, overload, type_check_only +from typing_extensions import Self, deprecated, disjoint_base __all__ = [ "register", @@ -185,10 +185,12 @@ else: def getencoder(encoding: str) -> _Encoder: ... def getdecoder(encoding: str) -> _Decoder: ... def getincrementalencoder(encoding: str) -> _IncrementalEncoder: ... + @overload def getincrementaldecoder(encoding: _BufferedEncoding) -> _BufferedIncrementalDecoder: ... @overload def getincrementaldecoder(encoding: str) -> _IncrementalDecoder: ... + def getreader(encoding: str) -> _StreamReader: ... def getwriter(encoding: str) -> _StreamWriter: ... @deprecated("Deprecated since Python 3.14. Use `open()` instead.") diff --git a/mypy/typeshed/stdlib/collections/__init__.pyi b/mypy/typeshed/stdlib/collections/__init__.pyi index 95f13b0c8dd2b..d9c2e342c5871 100644 --- a/mypy/typeshed/stdlib/collections/__init__.pyi +++ b/mypy/typeshed/stdlib/collections/__init__.pyi @@ -1,25 +1,24 @@ import sys from _collections_abc import dict_items, dict_keys, dict_values from _typeshed import SupportsItems, SupportsKeysAndGetItem, SupportsRichComparison, SupportsRichComparisonT +from collections.abc import ( + Callable, + ItemsView, + Iterable, + Iterator, + KeysView, + Mapping, + MutableMapping, + MutableSequence, + Sequence, + ValuesView, +) from types import GenericAlias from typing import Any, ClassVar, Generic, NoReturn, SupportsIndex, TypeVar, final, overload, type_check_only from typing_extensions import Self, disjoint_base -if sys.version_info >= (3, 10): - from collections.abc import ( - Callable, - ItemsView, - Iterable, - Iterator, - KeysView, - Mapping, - MutableMapping, - MutableSequence, - Sequence, - ValuesView, - ) -else: - from _collections_abc import * +if sys.version_info >= (3, 15): + from builtins import frozendict __all__ = ["ChainMap", "Counter", "OrderedDict", "UserDict", "UserList", "UserString", "defaultdict", "deque", "namedtuple"] @@ -44,6 +43,7 @@ def namedtuple( class UserDict(MutableMapping[_KT, _VT]): data: dict[_KT, _VT] + # __init__ should be kept roughly in line with `dict.__init__`, which has the same semantics @overload def __init__(self, dict: None = None, /) -> None: ... @@ -73,6 +73,7 @@ class UserDict(MutableMapping[_KT, _VT]): def __init__(self: UserDict[str, str], iterable: Iterable[list[str]], /) -> None: ... @overload def __init__(self: UserDict[bytes, bytes], iterable: Iterable[list[bytes]], /) -> None: ... + def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... def __setitem__(self, key: _KT, item: _VT) -> None: ... @@ -91,19 +92,23 @@ class UserDict(MutableMapping[_KT, _VT]): @classmethod @overload def fromkeys(cls, iterable: Iterable[_T], value: _S) -> UserDict[_T, _S]: ... + @overload def __or__(self, other: UserDict[_KT, _VT] | dict[_KT, _VT]) -> Self: ... @overload def __or__(self, other: UserDict[_T1, _T2] | dict[_T1, _T2]) -> UserDict[_KT | _T1, _VT | _T2]: ... + @overload def __ror__(self, other: UserDict[_KT, _VT] | dict[_KT, _VT]) -> Self: ... @overload def __ror__(self, other: UserDict[_T1, _T2] | dict[_T1, _T2]) -> UserDict[_KT | _T1, _VT | _T2]: ... + # UserDict.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... @overload def __ior__(self, other: Iterable[tuple[_KT, _VT]]) -> Self: ... + if sys.version_info >= (3, 12): @overload def get(self, key: _KT, default: None = None) -> _VT | None: ... @@ -114,10 +119,12 @@ class UserDict(MutableMapping[_KT, _VT]): class UserList(MutableSequence[_T]): data: list[_T] + @overload def __init__(self, initlist: None = None) -> None: ... @overload def __init__(self, initlist: Iterable[_T]) -> None: ... + __hash__: ClassVar[None] # type: ignore[assignment] def __lt__(self, other: list[_T] | UserList[_T]) -> bool: ... def __le__(self, other: list[_T] | UserList[_T]) -> bool: ... @@ -126,14 +133,17 @@ class UserList(MutableSequence[_T]): def __eq__(self, other: object) -> bool: ... def __contains__(self, item: object) -> bool: ... def __len__(self) -> int: ... + @overload def __getitem__(self, i: SupportsIndex) -> _T: ... @overload def __getitem__(self, i: slice[SupportsIndex | None]) -> Self: ... + @overload def __setitem__(self, i: SupportsIndex, item: _T) -> None: ... @overload def __setitem__(self, i: slice[SupportsIndex | None], item: Iterable[_T]) -> None: ... + def __delitem__(self, i: SupportsIndex | slice[SupportsIndex | None]) -> None: ... def __add__(self, other: Iterable[_T]) -> Self: ... def __radd__(self, other: Iterable[_T]) -> Self: ... @@ -152,11 +162,13 @@ class UserList(MutableSequence[_T]): # to `list.index`. In order to give more precise types, we pretend that the # `item` argument is positional-only. def index(self, item: _T, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int: ... + # All arguments are passed to `list.sort` at runtime, so the signature should be kept in line with `list.sort`. @overload def sort(self: UserList[SupportsRichComparisonT], *, key: None = None, reverse: bool = False) -> None: ... @overload def sort(self, *, key: Callable[[_T], SupportsRichComparison], reverse: bool = False) -> None: ... + def extend(self, other: Iterable[_T]) -> None: ... class UserString(Sequence[UserString]): @@ -235,10 +247,12 @@ class UserString(Sequence[UserString]): class deque(MutableSequence[_T]): @property def maxlen(self) -> int | None: ... + @overload def __init__(self, *, maxlen: int | None = None) -> None: ... @overload def __init__(self, iterable: Iterable[_T], maxlen: int | None = None) -> None: ... + def append(self, x: _T, /) -> None: ... def appendleft(self, x: _T, /) -> None: ... def copy(self) -> Self: ... @@ -280,17 +294,20 @@ class Counter(dict[_T, int], Generic[_T]): def __init__(self, mapping: SupportsKeysAndGetItem[_T, int], /) -> None: ... @overload def __init__(self, iterable: Iterable[_T], /) -> None: ... + def copy(self) -> Self: ... def elements(self) -> Iterator[_T]: ... def most_common(self, n: int | None = None) -> list[tuple[_T, int]]: ... @classmethod def fromkeys(cls, iterable: Any, v: int | None = None) -> NoReturn: ... # type: ignore[override] + @overload def subtract(self, iterable: None = None, /) -> None: ... @overload def subtract(self, mapping: Mapping[_T, int], /) -> None: ... @overload def subtract(self, iterable: Iterable[_T], /) -> None: ... + # Unlike dict.update(), use Mapping instead of SupportsKeysAndGetItem for the first overload # (source code does an `isinstance(other, Mapping)` check) # @@ -303,16 +320,23 @@ class Counter(dict[_T, int], Generic[_T]): def update(self, iterable: Iterable[_T], /, **kwargs: int) -> None: ... @overload def update(self, iterable: None = None, /, **kwargs: int) -> None: ... + + def total(self) -> int: ... def __missing__(self, key: _T) -> int: ... def __delitem__(self, elem: object) -> None: ... - if sys.version_info >= (3, 10): - def __eq__(self, other: object) -> bool: ... - def __ne__(self, other: object) -> bool: ... - + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __le__(self, other: Counter[Any]) -> bool: ... + def __lt__(self, other: Counter[Any]) -> bool: ... + def __ge__(self, other: Counter[Any]) -> bool: ... + def __gt__(self, other: Counter[Any]) -> bool: ... def __add__(self, other: Counter[_S]) -> Counter[_T | _S]: ... def __sub__(self, other: Counter[_T]) -> Counter[_T]: ... def __and__(self, other: Counter[_T]) -> Counter[_T]: ... def __or__(self, other: Counter[_S]) -> Counter[_T | _S]: ... # type: ignore[override] + if sys.version_info >= (3, 15): + def __xor__(self, other: Counter[_S]) -> Counter[_T | _S]: ... # type: ignore[override] + def __pos__(self) -> Counter[_T]: ... def __neg__(self) -> Counter[_T]: ... # several type: ignores because __iadd__ is supposedly incompatible with __add__, etc. @@ -320,12 +344,8 @@ class Counter(dict[_T, int], Generic[_T]): def __isub__(self, other: SupportsItems[_T, int]) -> Self: ... def __iand__(self, other: SupportsItems[_T, int]) -> Self: ... def __ior__(self, other: SupportsItems[_T, int]) -> Self: ... # type: ignore[override,misc] - if sys.version_info >= (3, 10): - def total(self) -> int: ... - def __le__(self, other: Counter[Any]) -> bool: ... - def __lt__(self, other: Counter[Any]) -> bool: ... - def __ge__(self, other: Counter[Any]) -> bool: ... - def __gt__(self, other: Counter[Any]) -> bool: ... + if sys.version_info >= (3, 15): + def __ixor__(self, other: Counter[_T]) -> Self: ... # type: ignore[misc] # The pure-Python implementations of the "views" classes # These are exposed at runtime in `collections/__init__.py` @@ -366,6 +386,7 @@ class OrderedDict(dict[_KT, _VT]): def keys(self) -> _odict_keys[_KT, _VT]: ... def items(self) -> _odict_items[_KT, _VT]: ... def values(self) -> _odict_values[_KT, _VT]: ... + # The signature of OrderedDict.fromkeys should be kept in line with `dict.fromkeys`, modulo positional-only differences. # Like dict.fromkeys, its true signature is not expressible in the current type system. # See #3800 & https://github.com/python/typing/issues/548#issuecomment-683336963. @@ -375,11 +396,13 @@ class OrderedDict(dict[_KT, _VT]): @classmethod @overload def fromkeys(cls, iterable: Iterable[_T], value: _S) -> OrderedDict[_T, _S]: ... + # Keep OrderedDict.setdefault in line with MutableMapping.setdefault, modulo positional-only differences. @overload def setdefault(self: OrderedDict[_KT, _T | None], key: _KT, default: None = None) -> _T | None: ... @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... + # Same as dict.pop, but accepts keyword arguments @overload def pop(self, key: _KT) -> _VT: ... @@ -387,19 +410,36 @@ class OrderedDict(dict[_KT, _VT]): def pop(self, key: _KT, default: _VT) -> _VT: ... @overload def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def __eq__(self, value: object, /) -> bool: ... - @overload - def __or__(self, value: dict[_KT, _VT], /) -> Self: ... - @overload - def __or__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... - @overload - def __ror__(self, value: dict[_KT, _VT], /) -> Self: ... - @overload - def __ror__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] + + if sys.version_info >= (3, 15): + @overload + def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> Self: ... + @overload + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... + + @overload # type: ignore[override] + def __ror__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> Self: ... # type: ignore[override,misc] + @overload + def __ror__( # type: ignore[misc] + self, value: dict[_T1, _T2] | frozendict[_T1, _T2], / + ) -> OrderedDict[_KT | _T1, _VT | _T2]: ... + else: + @overload + def __or__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __or__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... + + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> Self: ... + @overload + def __ror__(self, value: dict[_T1, _T2], /) -> OrderedDict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] @disjoint_base class defaultdict(dict[_KT, _VT]): default_factory: Callable[[], _VT] | None + @overload def __init__(self) -> None: ... @overload @@ -433,14 +473,19 @@ class defaultdict(dict[_KT, _VT]): /, **kwargs: _VT, ) -> None: ... + def __missing__(self, key: _KT, /) -> _VT: ... def __copy__(self) -> Self: ... def copy(self) -> Self: ... - @overload + + # defaultdict rejects frozendict in its direct __or__/__ror__ methods, even though dict accepts it. + # See https://github.com/python/cpython/issues/149534. + @overload # type: ignore[override] def __or__(self, value: dict[_KT, _VT], /) -> Self: ... @overload def __or__(self, value: dict[_T1, _T2], /) -> defaultdict[_KT | _T1, _VT | _T2]: ... - @overload + + @overload # type: ignore[override] def __ror__(self, value: dict[_KT, _VT], /) -> Self: ... @overload def __ror__(self, value: dict[_T1, _T2], /) -> defaultdict[_KT | _T1, _VT | _T2]: ... # type: ignore[misc] @@ -457,25 +502,30 @@ class ChainMap(MutableMapping[_KT, _VT]): def __iter__(self) -> Iterator[_KT]: ... def __len__(self) -> int: ... def __contains__(self, key: object) -> bool: ... + @overload def get(self, key: _KT, default: None = None) -> _VT | None: ... @overload def get(self, key: _KT, default: _VT) -> _VT: ... @overload def get(self, key: _KT, default: _T) -> _VT | _T: ... + def __missing__(self, key: _KT) -> _VT: ... # undocumented def __bool__(self) -> bool: ... + # Keep ChainMap.setdefault in line with MutableMapping.setdefault, modulo positional-only differences. @overload def setdefault(self: ChainMap[_KT, _T | None], key: _KT, default: None = None) -> _T | None: ... @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... + @overload def pop(self, key: _KT) -> _VT: ... @overload def pop(self, key: _KT, default: _VT) -> _VT: ... @overload def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def copy(self) -> Self: ... __copy__ = copy # All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, @@ -496,14 +546,17 @@ class ChainMap(MutableMapping[_KT, _VT]): @classmethod @overload def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> ChainMap[_T, _S]: ... + @overload def __or__(self, other: Mapping[_KT, _VT]) -> Self: ... @overload def __or__(self, other: Mapping[_T1, _T2]) -> ChainMap[_KT | _T1, _VT | _T2]: ... + @overload def __ror__(self, other: Mapping[_KT, _VT]) -> Self: ... @overload def __ror__(self, other: Mapping[_T1, _T2]) -> ChainMap[_KT | _T1, _VT | _T2]: ... + # ChainMap.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... diff --git a/mypy/typeshed/stdlib/colorsys.pyi b/mypy/typeshed/stdlib/colorsys.pyi index 4afcb5392b58e..d4edab12ecc70 100644 --- a/mypy/typeshed/stdlib/colorsys.pyi +++ b/mypy/typeshed/stdlib/colorsys.pyi @@ -9,7 +9,7 @@ def hls_to_rgb(h: float, l: float, s: float) -> tuple[float, float, float]: ... def rgb_to_hsv(r: float, g: float, b: float) -> tuple[float, float, float]: ... def hsv_to_rgb(h: float, s: float, v: float) -> tuple[float, float, float]: ... -# TODO: undocumented +# undocumented ONE_SIXTH: Final[float] ONE_THIRD: Final[float] TWO_THIRD: Final[float] diff --git a/mypy/typeshed/stdlib/compileall.pyi b/mypy/typeshed/stdlib/compileall.pyi index 8972d50a4a634..49a4c69dd3fb0 100644 --- a/mypy/typeshed/stdlib/compileall.pyi +++ b/mypy/typeshed/stdlib/compileall.pyi @@ -1,4 +1,3 @@ -import sys from _typeshed import StrPath from py_compile import PycInvalidationMode from typing import Any, Protocol, type_check_only @@ -9,74 +8,38 @@ __all__ = ["compile_dir", "compile_file", "compile_path"] class _SupportsSearch(Protocol): def search(self, string: str, /) -> Any: ... -if sys.version_info >= (3, 10): - def compile_dir( - dir: StrPath, - maxlevels: int | None = None, - ddir: StrPath | None = None, - force: bool = False, - rx: _SupportsSearch | None = None, - quiet: int = 0, - legacy: bool = False, - optimize: int = -1, - workers: int = 1, - invalidation_mode: PycInvalidationMode | None = None, - *, - stripdir: StrPath | None = None, - prependdir: StrPath | None = None, - limit_sl_dest: StrPath | None = None, - hardlink_dupes: bool = False, - ) -> bool: ... - def compile_file( - fullname: StrPath, - ddir: StrPath | None = None, - force: bool = False, - rx: _SupportsSearch | None = None, - quiet: int = 0, - legacy: bool = False, - optimize: int = -1, - invalidation_mode: PycInvalidationMode | None = None, - *, - stripdir: StrPath | None = None, - prependdir: StrPath | None = None, - limit_sl_dest: StrPath | None = None, - hardlink_dupes: bool = False, - ) -> bool: ... - -else: - def compile_dir( - dir: StrPath, - maxlevels: int | None = None, - ddir: StrPath | None = None, - force: bool = False, - rx: _SupportsSearch | None = None, - quiet: int = 0, - legacy: bool = False, - optimize: int = -1, - workers: int = 1, - invalidation_mode: PycInvalidationMode | None = None, - *, - stripdir: str | None = None, # https://bugs.python.org/issue40447 - prependdir: StrPath | None = None, - limit_sl_dest: StrPath | None = None, - hardlink_dupes: bool = False, - ) -> bool: ... - def compile_file( - fullname: StrPath, - ddir: StrPath | None = None, - force: bool = False, - rx: _SupportsSearch | None = None, - quiet: int = 0, - legacy: bool = False, - optimize: int = -1, - invalidation_mode: PycInvalidationMode | None = None, - *, - stripdir: str | None = None, # https://bugs.python.org/issue40447 - prependdir: StrPath | None = None, - limit_sl_dest: StrPath | None = None, - hardlink_dupes: bool = False, - ) -> bool: ... - +def compile_dir( + dir: StrPath, + maxlevels: int | None = None, + ddir: StrPath | None = None, + force: bool = False, + rx: _SupportsSearch | None = None, + quiet: int = 0, + legacy: bool = False, + optimize: int = -1, + workers: int = 1, + invalidation_mode: PycInvalidationMode | None = None, + *, + stripdir: StrPath | None = None, + prependdir: StrPath | None = None, + limit_sl_dest: StrPath | None = None, + hardlink_dupes: bool = False, +) -> bool: ... +def compile_file( + fullname: StrPath, + ddir: StrPath | None = None, + force: bool = False, + rx: _SupportsSearch | None = None, + quiet: int = 0, + legacy: bool = False, + optimize: int = -1, + invalidation_mode: PycInvalidationMode | None = None, + *, + stripdir: StrPath | None = None, + prependdir: StrPath | None = None, + limit_sl_dest: StrPath | None = None, + hardlink_dupes: bool = False, +) -> bool: ... def compile_path( skip_curdir: bool = ..., maxlevels: int = 0, diff --git a/mypy/typeshed/stdlib/compression/zstd/__init__.pyi b/mypy/typeshed/stdlib/compression/zstd/__init__.pyi index acfbe4913b5da..8673c59a41c22 100644 --- a/mypy/typeshed/stdlib/compression/zstd/__init__.pyi +++ b/mypy/typeshed/stdlib/compression/zstd/__init__.pyi @@ -52,6 +52,7 @@ def compress( def decompress( data: ReadableBuffer, zstd_dict: ZstdDict | tuple[ZstdDict, int] | None = None, options: Mapping[int, int] | None = None ) -> bytes: ... + @final class CompressionParameter(enum.IntEnum): compression_level = _zstd.ZSTD_c_compressionLevel diff --git a/mypy/typeshed/stdlib/compression/zstd/_zstdfile.pyi b/mypy/typeshed/stdlib/compression/zstd/_zstdfile.pyi index d37e6b1741664..b16b43c1da0ae 100644 --- a/mypy/typeshed/stdlib/compression/zstd/_zstdfile.pyi +++ b/mypy/typeshed/stdlib/compression/zstd/_zstdfile.pyi @@ -3,8 +3,7 @@ from collections.abc import Mapping from compression._common import _streams from compression.zstd import ZstdDict from io import TextIOWrapper, _WrappedBuffer -from typing import Literal, Protocol, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Literal, Protocol, TypeAlias, overload, type_check_only from _zstd import ZstdCompressor, _ZstdCompressorFlushBlock, _ZstdCompressorFlushFrame @@ -49,6 +48,7 @@ class ZstdFile(_streams.BaseStream): options: Mapping[int, int] | None = None, zstd_dict: ZstdDict | tuple[ZstdDict, int] | None = None, ) -> None: ... + def write(self, data: ReadableBuffer, /) -> int: ... def flush(self, mode: _ZstdCompressorFlushBlock | _ZstdCompressorFlushFrame = 1) -> bytes: ... # type: ignore[override] def read(self, size: int | None = -1) -> bytes: ... diff --git a/mypy/typeshed/stdlib/concurrent/futures/_base.pyi b/mypy/typeshed/stdlib/concurrent/futures/_base.pyi index be48a6e4289c8..05680b5de4619 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/_base.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/_base.pyi @@ -4,8 +4,8 @@ from _typeshed import Unused from collections.abc import Callable, Iterable, Iterator from logging import Logger from types import GenericAlias, TracebackType -from typing import Any, Final, Generic, NamedTuple, Protocol, TypeVar, type_check_only -from typing_extensions import ParamSpec, Self +from typing import Any, Final, Generic, NamedTuple, ParamSpec, Protocol, TypeVar, type_check_only +from typing_extensions import Self FIRST_COMPLETED: Final = "FIRST_COMPLETED" FIRST_EXCEPTION: Final = "FIRST_EXCEPTION" diff --git a/mypy/typeshed/stdlib/concurrent/futures/interpreter.pyi b/mypy/typeshed/stdlib/concurrent/futures/interpreter.pyi index e101022babcb6..f6925806a5eb9 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/interpreter.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/interpreter.pyi @@ -1,8 +1,8 @@ import sys from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor -from typing import Any, Literal, Protocol, overload, type_check_only -from typing_extensions import ParamSpec, Self, TypeAlias, TypeVar, TypeVarTuple, Unpack +from typing import Any, Literal, ParamSpec, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self, TypeVar, TypeVarTuple, Unpack _Task: TypeAlias = tuple[bytes, Literal["function", "script"]] _Ts = TypeVarTuple("_Ts") @@ -25,6 +25,7 @@ if sys.version_info >= (3, 14): class WorkerContext(ThreadWorkerContext): interp: Interpreter | None results: Queue | None + @overload # type: ignore[override] @classmethod def prepare( @@ -33,6 +34,7 @@ if sys.version_info >= (3, 14): @overload @classmethod def prepare(cls, initializer: Callable[[], object], initargs: tuple[()]) -> tuple[Callable[[], Self], _TaskFunc]: ... + def __init__(self, initdata: _Task) -> None: ... def __del__(self) -> None: ... def run(self, task: _Task) -> None: ... # type: ignore[override] @@ -52,6 +54,7 @@ if sys.version_info >= (3, 14): def prepare_context( cls, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]] ) -> tuple[Callable[[], WorkerContext], _TaskFunc]: ... + @overload def __init__( self, diff --git a/mypy/typeshed/stdlib/concurrent/futures/thread.pyi b/mypy/typeshed/stdlib/concurrent/futures/thread.pyi index 50a6a9c6f43ea..685bf1cfc104a 100644 --- a/mypy/typeshed/stdlib/concurrent/futures/thread.pyi +++ b/mypy/typeshed/stdlib/concurrent/futures/thread.pyi @@ -3,8 +3,8 @@ import sys from collections.abc import Callable, Iterable, Mapping, Set as AbstractSet from threading import Lock, Semaphore, Thread from types import GenericAlias -from typing import Any, Generic, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, TypeVarTuple, Unpack +from typing import Any, Generic, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, TypeVarTuple, Unpack from weakref import ref from ._base import BrokenExecutor, Executor, Future @@ -43,10 +43,12 @@ if sys.version_info >= (3, 14): def prepare( cls, initializer: Callable[[], object], initargs: tuple[()] ) -> tuple[Callable[[], Self], _ResolveTaskFunc]: ... + @overload def __init__(self, initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]]) -> None: ... @overload def __init__(self, initializer: Callable[[], object], initargs: tuple[()]) -> None: ... + def initialize(self) -> None: ... def finalize(self) -> None: ... def run(self, task: _Task) -> None: ... @@ -136,5 +138,6 @@ class ThreadPoolExecutor(Executor): initializer: Callable[[Unpack[_Ts]], object], initargs: tuple[Unpack[_Ts]], ) -> None: ... + def _adjust_thread_count(self) -> None: ... def _initializer_failed(self) -> None: ... diff --git a/mypy/typeshed/stdlib/concurrent/interpreters/__init__.pyi b/mypy/typeshed/stdlib/concurrent/interpreters/__init__.pyi index 171fadb2202be..d19db09642602 100644 --- a/mypy/typeshed/stdlib/concurrent/interpreters/__init__.pyi +++ b/mypy/typeshed/stdlib/concurrent/interpreters/__init__.pyi @@ -2,8 +2,8 @@ import sys import threading import types from collections.abc import Callable -from typing import Any, Literal, TypeVar -from typing_extensions import ParamSpec, Self +from typing import Any, Literal, ParamSpec, TypeVar +from typing_extensions import Self if sys.version_info >= (3, 14): # needed to satisfy pyright checks for Python <= 3.13 from _interpreters import ( diff --git a/mypy/typeshed/stdlib/concurrent/interpreters/_crossinterp.pyi b/mypy/typeshed/stdlib/concurrent/interpreters/_crossinterp.pyi index 50fe7cf0b4ba4..c8e29aaafa18a 100644 --- a/mypy/typeshed/stdlib/concurrent/interpreters/_crossinterp.pyi +++ b/mypy/typeshed/stdlib/concurrent/interpreters/_crossinterp.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable -from typing import Final, NewType -from typing_extensions import Never, Self, TypeAlias +from typing import Final, NewType, TypeAlias +from typing_extensions import Never, Self if sys.version_info >= (3, 14): # needed to satisfy pyright checks for Python <= 3.13 from _interpqueues import _UnboundOp diff --git a/mypy/typeshed/stdlib/configparser.pyi b/mypy/typeshed/stdlib/configparser.pyi index 9b3f02324b7fd..385336ec154ab 100644 --- a/mypy/typeshed/stdlib/configparser.pyi +++ b/mypy/typeshed/stdlib/configparser.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import BytesPath, GenericPath, MaybeNone, StrOrBytesPath, StrPath, SupportsWrite from collections.abc import Callable, ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence from re import Pattern -from typing import Any, AnyStr, ClassVar, Final, Literal, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias, deprecated +from typing import Any, AnyStr, ClassVar, Final, Literal, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import deprecated if sys.version_info >= (3, 14): __all__ = ( @@ -269,6 +269,7 @@ class RawConfigParser(_Parser): def has_section(self, section: _SectionName) -> bool: ... def options(self, section: _SectionName) -> list[str]: ... def has_option(self, section: _SectionName, option: str) -> bool: ... + @overload def read(self, filenames: GenericPath[AnyStr], encoding: str | None = None) -> list[AnyStr]: ... @overload @@ -277,12 +278,14 @@ class RawConfigParser(_Parser): def read(self, filenames: Iterable[BytesPath], encoding: str | None = None) -> list[bytes]: ... @overload def read(self, filenames: Iterable[StrOrBytesPath], encoding: str | None = None) -> list[str | bytes]: ... + def read_file(self, f: Iterable[str], source: str | None = None) -> None: ... def read_string(self, string: str, source: str = "") -> None: ... def read_dict(self, dictionary: Mapping[str, Mapping[str, Any]], source: str = "") -> None: ... if sys.version_info < (3, 12): @deprecated("Deprecated since Python 3.2; removed in Python 3.12. Use `parser.read_file()` instead.") def readfp(self, fp: Iterable[str], filename: str | None = None) -> None: ... + # These get* methods are partially applied (with the same names) in # SectionProxy; the stubs should be kept updated together @overload @@ -291,18 +294,21 @@ class RawConfigParser(_Parser): def getint( self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T ) -> int | _T: ... + @overload def getfloat(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> float: ... @overload def getfloat( self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T ) -> float | _T: ... + @overload def getboolean(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> bool: ... @overload def getboolean( self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T ) -> bool | _T: ... + def _get_conv( self, section: _SectionName, @@ -313,6 +319,7 @@ class RawConfigParser(_Parser): vars: _Section | None = None, fallback: _T = ..., ) -> _T: ... + # This is incompatible with MutableMapping so we ignore the type @overload # type: ignore[override] def get(self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None) -> str | MaybeNone: ... @@ -320,10 +327,12 @@ class RawConfigParser(_Parser): def get( self, section: _SectionName, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T ) -> str | _T | MaybeNone: ... + @overload def items(self, *, raw: bool = False, vars: _Section | None = None) -> ItemsView[str, SectionProxy]: ... @overload def items(self, section: _SectionName, raw: bool = False, vars: _Section | None = None) -> list[tuple[str, str]]: ... + def set(self, section: _SectionName, option: str, value: str | None = None) -> None: ... def write(self, fp: SupportsWrite[str], space_around_delimiters: bool = True) -> None: ... def remove_option(self, section: _SectionName, option: str) -> bool: ... @@ -357,6 +366,7 @@ class SectionProxy(MutableMapping[str, str]): def parser(self) -> RawConfigParser: ... @property def name(self) -> str: ... + # This is incompatible with MutableMapping so we ignore the type @overload # type: ignore[override] def get( @@ -380,20 +390,24 @@ class SectionProxy(MutableMapping[str, str]): _impl: Any | None = None, **kwargs: Any, # passed to the underlying parser's get() method ) -> str | _T: ... + # These are partially-applied version of the methods with the same names in # RawConfigParser; the stubs should be kept updated together @overload def getint(self, option: str, *, raw: bool = False, vars: _Section | None = None) -> int | None: ... @overload def getint(self, option: str, fallback: _T = ..., *, raw: bool = False, vars: _Section | None = None) -> int | _T: ... + @overload def getfloat(self, option: str, *, raw: bool = False, vars: _Section | None = None) -> float | None: ... @overload def getfloat(self, option: str, fallback: _T = ..., *, raw: bool = False, vars: _Section | None = None) -> float | _T: ... + @overload def getboolean(self, option: str, *, raw: bool = False, vars: _Section | None = None) -> bool | None: ... @overload def getboolean(self, option: str, fallback: _T = ..., *, raw: bool = False, vars: _Section | None = None) -> bool | _T: ... + # SectionProxy can have arbitrary attributes when custom converters are used def __getattr__(self, key: str) -> Callable[..., Any]: ... diff --git a/mypy/typeshed/stdlib/contextlib.pyi b/mypy/typeshed/stdlib/contextlib.pyi index 0670787a5db1b..73cdda3b8f342 100644 --- a/mypy/typeshed/stdlib/contextlib.pyi +++ b/mypy/typeshed/stdlib/contextlib.pyi @@ -4,10 +4,11 @@ from _typeshed import FileDescriptorOrPath, Unused from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator from types import TracebackType -from typing import Any, Generic, Protocol, TypeVar, overload, runtime_checkable, type_check_only -from typing_extensions import ParamSpec, Self, TypeAlias +from typing import Any, Generic, ParamSpec, Protocol, TypeAlias, TypeVar, overload, runtime_checkable, type_check_only +from typing_extensions import Self __all__ = [ + "aclosing", "contextmanager", "closing", "AbstractContextManager", @@ -22,9 +23,6 @@ __all__ = [ "nullcontext", ] -if sys.version_info >= (3, 10): - __all__ += ["aclosing"] - if sys.version_info >= (3, 11): __all__ += ["chdir"] @@ -88,31 +86,23 @@ class _GeneratorContextManager( def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ... -if sys.version_info >= (3, 10): - _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) - - class AsyncContextDecorator: - def _recreate_cm(self) -> Self: ... - def __call__(self, func: _AF) -> _AF: ... - - class _AsyncGeneratorContextManager( - _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], - AbstractAsyncContextManager[_T_co, bool | None], - AsyncContextDecorator, - ): - async def __aexit__( - self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool | None: ... - -else: - class _AsyncGeneratorContextManager( - _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], AbstractAsyncContextManager[_T_co, bool | None] - ): - async def __aexit__( - self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool | None: ... +_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) + +class AsyncContextDecorator: + def _recreate_cm(self) -> Self: ... + def __call__(self, func: _AF) -> _AF: ... + +class _AsyncGeneratorContextManager( + _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], + AbstractAsyncContextManager[_T_co, bool | None], + AsyncContextDecorator, +): + async def __aexit__( + self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool | None: ... def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ... + @type_check_only class _SupportsClose(Protocol): def close(self) -> object: ... @@ -123,16 +113,15 @@ class closing(AbstractContextManager[_SupportsCloseT, None]): def __init__(self, thing: _SupportsCloseT) -> None: ... def __exit__(self, *exc_info: Unused) -> None: ... -if sys.version_info >= (3, 10): - @type_check_only - class _SupportsAclose(Protocol): - def aclose(self) -> Awaitable[object]: ... +@type_check_only +class _SupportsAclose(Protocol): + def aclose(self) -> Awaitable[object]: ... - _SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose) +_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose) - class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]): - def __init__(self, thing: _SupportsAcloseT) -> None: ... - async def __aexit__(self, *exc_info: Unused) -> None: ... +class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]): + def __init__(self, thing: _SupportsAcloseT) -> None: ... + async def __aexit__(self, *exc_info: Unused) -> None: ... class suppress(AbstractContextManager[None, bool]): def __init__(self, *exceptions: type[BaseException]) -> None: ... @@ -199,27 +188,18 @@ class AsyncExitStack(_BaseExitStackAbstract[_ExitT_co]): self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> _ExitT_co: ... -if sys.version_info >= (3, 10): - class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]): - enter_result: _T - @overload - def __init__(self: nullcontext[None]) -> None: ... - @overload - def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 - def __enter__(self) -> _T: ... - def __exit__(self, *exctype: Unused) -> None: ... - async def __aenter__(self) -> _T: ... - async def __aexit__(self, *exctype: Unused) -> None: ... - -else: - class nullcontext(AbstractContextManager[_T, None]): - enter_result: _T - @overload - def __init__(self: nullcontext[None]) -> None: ... - @overload - def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 - def __enter__(self) -> _T: ... - def __exit__(self, *exctype: Unused) -> None: ... +class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]): + enter_result: _T + + @overload + def __init__(self: nullcontext[None]) -> None: ... + @overload + def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 + + def __enter__(self) -> _T: ... + def __exit__(self, *exctype: Unused) -> None: ... + async def __aenter__(self) -> _T: ... + async def __aexit__(self, *exctype: Unused) -> None: ... if sys.version_info >= (3, 11): _T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath) diff --git a/mypy/typeshed/stdlib/copyreg.pyi b/mypy/typeshed/stdlib/copyreg.pyi index 8f7fd957fc526..3bfc0de8158f1 100644 --- a/mypy/typeshed/stdlib/copyreg.pyi +++ b/mypy/typeshed/stdlib/copyreg.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable, Hashable -from typing import Any, SupportsInt, TypeVar -from typing_extensions import TypeAlias +from typing import Any, SupportsInt, TypeAlias, TypeVar _T = TypeVar("_T") _Reduce: TypeAlias = tuple[Callable[..., _T], tuple[Any, ...]] | tuple[Callable[..., _T], tuple[Any, ...], Any | None] diff --git a/mypy/typeshed/stdlib/csv.pyi b/mypy/typeshed/stdlib/csv.pyi index 4ed0ab1d83b82..f3b4286a6b495 100644 --- a/mypy/typeshed/stdlib/csv.pyi +++ b/mypy/typeshed/stdlib/csv.pyi @@ -19,11 +19,7 @@ from _csv import ( if sys.version_info >= (3, 12): from _csv import QUOTE_NOTNULL as QUOTE_NOTNULL, QUOTE_STRINGS as QUOTE_STRINGS -if sys.version_info >= (3, 10): - from _csv import Reader, Writer -else: - from _csv import _reader as Reader, _writer as Writer - +from _csv import Reader, Writer from _typeshed import SupportsWrite from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence from types import GenericAlias @@ -80,6 +76,7 @@ class DictReader(Iterator[dict[_T | Any, str | Any]], Generic[_T]): reader: Reader dialect: _DialectLike line_num: int + @overload def __init__( self, @@ -116,6 +113,7 @@ class DictReader(Iterator[dict[_T | Any, str | Any]], Generic[_T]): quoting: _QuotingType = 0, strict: bool = False, ) -> None: ... + def __iter__(self) -> Self: ... def __next__(self) -> dict[_T | Any, str | Any]: ... if sys.version_info >= (3, 12): diff --git a/mypy/typeshed/stdlib/ctypes/__init__.pyi b/mypy/typeshed/stdlib/ctypes/__init__.pyi index 8d048aa97e7d5..7eee1b760063f 100644 --- a/mypy/typeshed/stdlib/ctypes/__init__.pyi +++ b/mypy/typeshed/stdlib/ctypes/__init__.pyi @@ -26,8 +26,8 @@ from _ctypes import ( from _typeshed import StrPath, SupportsBool, SupportsLen from ctypes._endian import BigEndianStructure as BigEndianStructure, LittleEndianStructure as LittleEndianStructure from types import GenericAlias -from typing import Any, ClassVar, Final, Generic, Literal, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import Any, ClassVar, Final, Generic, Literal, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, deprecated if sys.platform == "win32": from _ctypes import FormatError as FormatError, get_last_error as get_last_error, set_last_error as set_last_error @@ -50,6 +50,7 @@ if sys.version_info >= (3, 14): def POINTER(cls: None) -> type[c_void_p]: ... @overload def POINTER(cls: type[_CT]) -> type[_Pointer[_CT]]: ... + def pointer(obj: _CT) -> _Pointer[_CT]: ... else: @@ -166,15 +167,12 @@ c_buffer = create_string_buffer def create_unicode_buffer(init: int | str, size: int | None = None) -> Array[c_wchar]: ... -if sys.version_info >= (3, 13): +if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def SetPointerType(pointer: type[_Pointer[Any]], cls: _CTypeBaseType) -> None: ... - @deprecated("Soft deprecated since Python 3.13. Use multiplication instead.") - def ARRAY(typ: _CT, len: int) -> Array[_CT]: ... -else: - def SetPointerType(pointer: type[_Pointer[Any]], cls: _CTypeBaseType) -> None: ... - def ARRAY(typ: _CT, len: int) -> Array[_CT]: ... +@deprecated("Soft deprecated since Python 3.13. Use multiplication instead.") +def ARRAY(typ: _CT, len: int) -> Array[_CT]: ... if sys.platform == "win32": def DllCanUnloadNow() -> int: ... @@ -221,86 +219,146 @@ class py_object(_CanCastTo, _SimpleCData[_T]): class c_bool(_SimpleCData[bool]): _type_: ClassVar[Literal["?"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] def __init__(self, value: SupportsBool | SupportsLen | None = ...) -> None: ... class c_byte(_SimpleCData[int]): _type_: ClassVar[Literal["b"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_ubyte(_SimpleCData[int]): _type_: ClassVar[Literal["B"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_short(_SimpleCData[int]): _type_: ClassVar[Literal["h"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_ushort(_SimpleCData[int]): _type_: ClassVar[Literal["H"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_long(_SimpleCData[int]): _type_: ClassVar[Literal["l"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_ulong(_SimpleCData[int]): _type_: ClassVar[Literal["L"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_int(_SimpleCData[int]): # can be an alias for c_long _type_: ClassVar[Literal["i", "l"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_uint(_SimpleCData[int]): # can be an alias for c_ulong _type_: ClassVar[Literal["I", "L"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_longlong(_SimpleCData[int]): # can be an alias for c_long _type_: ClassVar[Literal["q", "l"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_ulonglong(_SimpleCData[int]): # can be an alias for c_ulong _type_: ClassVar[Literal["Q", "L"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] c_int8 = c_byte c_uint8 = c_ubyte class c_int16(_SimpleCData[int]): # can be an alias for c_short or c_int _type_: ClassVar[Literal["h", "i"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_uint16(_SimpleCData[int]): # can be an alias for c_ushort or c_uint _type_: ClassVar[Literal["H", "I"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_int32(_SimpleCData[int]): # can be an alias for c_int or c_long _type_: ClassVar[Literal["i", "l"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_uint32(_SimpleCData[int]): # can be an alias for c_uint or c_ulong _type_: ClassVar[Literal["I", "L"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_int64(_SimpleCData[int]): # can be an alias for c_long or c_longlong _type_: ClassVar[Literal["l", "q"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_uint64(_SimpleCData[int]): # can be an alias for c_ulong or c_ulonglong _type_: ClassVar[Literal["L", "Q"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_ssize_t(_SimpleCData[int]): # alias for c_int, c_long, or c_longlong _type_: ClassVar[Literal["i", "l", "q"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_size_t(_SimpleCData[int]): # alias for c_uint, c_ulong, or c_ulonglong _type_: ClassVar[Literal["I", "L", "Q"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_float(_SimpleCData[float]): _type_: ClassVar[Literal["f"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_double(_SimpleCData[float]): _type_: ClassVar[Literal["d"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_longdouble(_SimpleCData[float]): # can be an alias for c_double _type_: ClassVar[Literal["d", "g"]] if sys.version_info >= (3, 14) and sys.platform != "win32": + # NOTE: currently (3.14.4) the `__ctype_{be,le}__` attributes of these complex types are missing at runtime: + # https://github.com/python/cpython/issues/148464 + class c_double_complex(_SimpleCData[complex]): - _type_: ClassVar[Literal["D"]] + if sys.version_info >= (3, 15): + _type_: ClassVar[Literal["Zd"]] + else: + _type_: ClassVar[Literal["D"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_float_complex(_SimpleCData[complex]): - _type_: ClassVar[Literal["F"]] + if sys.version_info >= (3, 15): + _type_: ClassVar[Literal["Zf"]] + else: + _type_: ClassVar[Literal["F"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] class c_longdouble_complex(_SimpleCData[complex]): - _type_: ClassVar[Literal["G"]] + if sys.version_info >= (3, 15): + _type_: ClassVar[Literal["Zg"]] + else: + _type_: ClassVar[Literal["G"]] class c_char(_SimpleCData[bytes]): _type_: ClassVar[Literal["c"]] + __ctype_be__: ClassVar[type[Self]] + __ctype_le__: ClassVar[type[Self]] def __init__(self, value: int | bytes | bytearray = ...) -> None: ... class c_char_p(_PointerLike, _SimpleCData[bytes | None]): diff --git a/mypy/typeshed/stdlib/ctypes/wintypes.pyi b/mypy/typeshed/stdlib/ctypes/wintypes.pyi index 0f0d61a396d5f..b94c5e74148a2 100644 --- a/mypy/typeshed/stdlib/ctypes/wintypes.pyi +++ b/mypy/typeshed/stdlib/ctypes/wintypes.pyi @@ -21,8 +21,8 @@ from ctypes import ( c_wchar, c_wchar_p, ) -from typing import Any, Final, TypeVar -from typing_extensions import Self, TypeAlias +from typing import Any, Final, TypeAlias, TypeVar +from typing_extensions import Self if sys.version_info >= (3, 12): from ctypes import c_ubyte diff --git a/mypy/typeshed/stdlib/curses/__init__.pyi b/mypy/typeshed/stdlib/curses/__init__.pyi index 3e32487ad99f2..cf50481108786 100644 --- a/mypy/typeshed/stdlib/curses/__init__.pyi +++ b/mypy/typeshed/stdlib/curses/__init__.pyi @@ -1,10 +1,8 @@ -import sys from _curses import * from _curses import window as window from _typeshed import structseq from collections.abc import Callable -from typing import Final, TypeVar, final, type_check_only -from typing_extensions import Concatenate, ParamSpec +from typing import Concatenate, Final, ParamSpec, TypeVar, final, type_check_only # NOTE: The _curses module is ordinarily only available on Unix, but the # windows-curses package makes it available on Windows as well with the same @@ -30,8 +28,7 @@ def wrapper(func: Callable[Concatenate[window, _P], _T], /, *arg: _P.args, **kwd @final @type_check_only class _ncurses_version(structseq[int], tuple[int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("major", "minor", "patch") + __match_args__: Final = ("major", "minor", "patch") @property def major(self) -> int: ... diff --git a/mypy/typeshed/stdlib/dataclasses.pyi b/mypy/typeshed/stdlib/dataclasses.pyi index 3a1c8cb5d62dd..1a7b8fd645589 100644 --- a/mypy/typeshed/stdlib/dataclasses.pyi +++ b/mypy/typeshed/stdlib/dataclasses.pyi @@ -17,6 +17,7 @@ __all__ = [ "Field", "FrozenInstanceError", "InitVar", + "KW_ONLY", "MISSING", "fields", "asdict", @@ -26,9 +27,6 @@ __all__ = [ "is_dataclass", ] -if sys.version_info >= (3, 10): - __all__ += ["KW_ONLY"] - _DataclassT = TypeVar("_DataclassT", bound=DataclassInstance) @type_check_only @@ -60,13 +58,13 @@ class _MISSING_TYPE(enum.Enum): MISSING: Final = _MISSING_TYPE.MISSING -if sys.version_info >= (3, 10): - class KW_ONLY: ... +class KW_ONLY: ... @overload def asdict(obj: DataclassInstance) -> dict[str, Any]: ... @overload def asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T: ... + @overload def astuple(obj: DataclassInstance) -> tuple[Any, ...]: ... @overload @@ -105,8 +103,7 @@ if sys.version_info >= (3, 11): slots: bool = False, weakref_slot: bool = False, ) -> Callable[[type[_T]], type[_T]]: ... - -elif sys.version_info >= (3, 10): +else: @overload def dataclass( cls: type[_T], @@ -138,32 +135,6 @@ elif sys.version_info >= (3, 10): slots: bool = False, ) -> Callable[[type[_T]], type[_T]]: ... -else: - @overload - def dataclass( - cls: type[_T], - /, - *, - init: bool = True, - repr: bool = True, - eq: bool = True, - order: bool = False, - unsafe_hash: bool = False, - frozen: bool = False, - ) -> type[_T]: ... - @overload - def dataclass( - cls: None = None, - /, - *, - init: bool = True, - repr: bool = True, - eq: bool = True, - order: bool = False, - unsafe_hash: bool = False, - frozen: bool = False, - ) -> Callable[[type[_T]], type[_T]]: ... - # See https://github.com/python/mypy/issues/10750 @type_check_only class _DefaultFactory(Protocol[_T_co]): @@ -185,7 +156,7 @@ class Field(Generic[_T]): "doc", "_field_type", ) - elif sys.version_info >= (3, 10): + else: __slots__ = ( "name", "type", @@ -199,8 +170,6 @@ class Field(Generic[_T]): "kw_only", "_field_type", ) - else: - __slots__ = ("name", "type", "default", "default_factory", "repr", "hash", "init", "compare", "metadata", "_field_type") name: str type: Type[_T] | str | Any default: _T | Literal[_MISSING_TYPE.MISSING] @@ -214,8 +183,7 @@ class Field(Generic[_T]): if sys.version_info >= (3, 14): doc: str | None - if sys.version_info >= (3, 10): - kw_only: bool | Literal[_MISSING_TYPE.MISSING] + kw_only: bool | Literal[_MISSING_TYPE.MISSING] if sys.version_info >= (3, 14): def __init__( @@ -230,18 +198,6 @@ class Field(Generic[_T]): kw_only: bool, doc: str | None, ) -> None: ... - elif sys.version_info >= (3, 10): - def __init__( - self, - default: _T, - default_factory: Callable[[], _T], - init: bool, - repr: bool, - hash: bool | None, - compare: bool, - metadata: Mapping[Any, Any], - kw_only: bool, - ) -> None: ... else: def __init__( self, @@ -252,6 +208,7 @@ class Field(Generic[_T]): hash: bool | None, compare: bool, metadata: Mapping[Any, Any], + kw_only: bool, ) -> None: ... def __set_name__(self, owner: Type[Any], name: str) -> None: ... @@ -299,8 +256,7 @@ if sys.version_info >= (3, 14): kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ..., doc: str | None = None, ) -> Any: ... - -elif sys.version_info >= (3, 10): +else: @overload # `default` and `default_factory` are optional and mutually exclusive. def field( *, @@ -338,41 +294,6 @@ elif sys.version_info >= (3, 10): kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ..., ) -> Any: ... -else: - @overload # `default` and `default_factory` are optional and mutually exclusive. - def field( - *, - default: _T, - default_factory: Literal[_MISSING_TYPE.MISSING] = ..., - init: bool = True, - repr: bool = True, - hash: bool | None = None, - compare: bool = True, - metadata: Mapping[Any, Any] | None = None, - ) -> _T: ... - @overload - def field( - *, - default: Literal[_MISSING_TYPE.MISSING] = ..., - default_factory: Callable[[], _T], - init: bool = True, - repr: bool = True, - hash: bool | None = None, - compare: bool = True, - metadata: Mapping[Any, Any] | None = None, - ) -> _T: ... - @overload - def field( - *, - default: Literal[_MISSING_TYPE.MISSING] = ..., - default_factory: Literal[_MISSING_TYPE.MISSING] = ..., - init: bool = True, - repr: bool = True, - hash: bool | None = None, - compare: bool = True, - metadata: Mapping[Any, Any] | None = None, - ) -> Any: ... - def fields(class_or_instance: DataclassInstance | type[DataclassInstance]) -> tuple[Field[Any], ...]: ... # HACK: `obj: Never` typing matches if object argument is using `Any` type. @@ -389,6 +310,7 @@ class InitVar(Generic[_T]): __slots__ = ("type",) type: Type[_T] def __init__(self, type: Type[_T]) -> None: ... + @overload def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore[reportInvalidTypeForm] @overload @@ -454,7 +376,7 @@ elif sys.version_info >= (3, 11): weakref_slot: bool = False, ) -> type: ... -elif sys.version_info >= (3, 10): +else: def make_dataclass( cls_name: str, fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], @@ -472,19 +394,4 @@ elif sys.version_info >= (3, 10): slots: bool = False, ) -> type: ... -else: - def make_dataclass( - cls_name: str, - fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], - *, - bases: tuple[type, ...] = (), - namespace: dict[str, Any] | None = None, - init: bool = True, - repr: bool = True, - eq: bool = True, - order: bool = False, - unsafe_hash: bool = False, - frozen: bool = False, - ) -> type: ... - def replace(obj: _DataclassT, /, **changes: Any) -> _DataclassT: ... diff --git a/mypy/typeshed/stdlib/datetime.pyi b/mypy/typeshed/stdlib/datetime.pyi index 8a0536c006d57..95c62a9c1b712 100644 --- a/mypy/typeshed/stdlib/datetime.pyi +++ b/mypy/typeshed/stdlib/datetime.pyi @@ -1,8 +1,8 @@ import sys from abc import abstractmethod from time import struct_time -from typing import ClassVar, Final, NoReturn, SupportsIndex, final, overload, type_check_only -from typing_extensions import CapsuleType, Self, TypeAlias, deprecated, disjoint_base +from typing import ClassVar, Final, NoReturn, SupportsIndex, TypeAlias, final, overload, type_check_only +from typing_extensions import CapsuleType, Self, deprecated, disjoint_base if sys.version_info >= (3, 11): __all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR", "UTC") @@ -63,8 +63,14 @@ class date: def today(cls) -> Self: ... @classmethod def fromordinal(cls, n: int, /) -> Self: ... - @classmethod - def fromisoformat(cls, date_string: str, /) -> Self: ... + + if sys.version_info >= (3, 15): + @classmethod + def fromisoformat(cls, string: str, /) -> Self: ... + else: + @classmethod + def fromisoformat(cls, date_string: str, /) -> Self: ... + @classmethod def fromisocalendar(cls, year: int, week: int, day: int) -> Self: ... @property @@ -76,8 +82,12 @@ class date: def ctime(self) -> str: ... if sys.version_info >= (3, 14): - @classmethod - def strptime(cls, date_string: str, format: str, /) -> Self: ... + if sys.version_info >= (3, 15): + @classmethod + def strptime(cls, string: str, format: str, /) -> Self: ... + else: + @classmethod + def strptime(cls, date_string: str, format: str, /) -> Self: ... # On <3.12, the name of the parameter in the pure-Python implementation # didn't match the name in the C implementation, @@ -102,12 +112,14 @@ class date: def __eq__(self, value: object, /) -> bool: ... def __add__(self, value: timedelta, /) -> Self: ... def __radd__(self, value: timedelta, /) -> Self: ... + @overload def __sub__(self, value: datetime, /) -> NoReturn: ... @overload def __sub__(self, value: Self, /) -> timedelta: ... @overload def __sub__(self, value: timedelta, /) -> Self: ... + def __hash__(self) -> int: ... def weekday(self) -> int: ... def isoweekday(self) -> int: ... @@ -147,12 +159,21 @@ class time: def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... def isoformat(self, timespec: str = "auto") -> str: ... - @classmethod - def fromisoformat(cls, time_string: str, /) -> Self: ... - if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): @classmethod - def strptime(cls, date_string: str, format: str, /) -> Self: ... + def fromisoformat(cls, string: str, /) -> Self: ... + else: + @classmethod + def fromisoformat(cls, time_string: str, /) -> Self: ... + + if sys.version_info >= (3, 14): + if sys.version_info >= (3, 15): + @classmethod + def strptime(cls, string: str, format: str, /) -> Self: ... + else: + @classmethod + def strptime(cls, date_string: str, format: str, /) -> Self: ... # On <3.12, the name of the parameter in the pure-Python implementation # didn't match the name in the C implementation, @@ -224,14 +245,17 @@ class timedelta: def __abs__(self) -> timedelta: ... def __mul__(self, value: float, /) -> timedelta: ... def __rmul__(self, value: float, /) -> timedelta: ... + @overload def __floordiv__(self, value: timedelta, /) -> int: ... @overload def __floordiv__(self, value: int, /) -> timedelta: ... + @overload def __truediv__(self, value: timedelta, /) -> float: ... @overload def __truediv__(self, value: float, /) -> timedelta: ... + def __mod__(self, value: timedelta, /) -> timedelta: ... def __divmod__(self, value: timedelta, /) -> tuple[int, timedelta]: ... def __le__(self, value: timedelta, /) -> bool: ... @@ -291,6 +315,10 @@ class datetime(date): def utcnow(cls) -> Self: ... @classmethod def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo | None = ...) -> Self: ... + if sys.version_info >= (3, 15): + @classmethod + def fromisoformat(cls, string: str, /) -> Self: ... + def timestamp(self) -> float: ... def utctimetuple(self) -> struct_time: ... def date(self) -> _Date: ... @@ -327,8 +355,14 @@ class datetime(date): ) -> Self: ... def astimezone(self, tz: _TzInfo | None = None) -> Self: ... def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: ... - @classmethod - def strptime(cls, date_string: str, format: str, /) -> Self: ... + + if sys.version_info >= (3, 15): + @classmethod + def strptime(cls, string: str, format: str, /) -> Self: ... + else: + @classmethod + def strptime(cls, date_string: str, format: str, /) -> Self: ... + def utcoffset(self) -> timedelta | None: ... def tzname(self) -> str | None: ... def dst(self) -> timedelta | None: ... @@ -338,6 +372,7 @@ class datetime(date): def __gt__(self, value: datetime, /) -> bool: ... # type: ignore[override] def __eq__(self, value: object, /) -> bool: ... def __hash__(self) -> int: ... + @overload # type: ignore[override] def __sub__(self, value: Self, /) -> timedelta: ... @overload diff --git a/mypy/typeshed/stdlib/dbm/__init__.pyi b/mypy/typeshed/stdlib/dbm/__init__.pyi index 7cbb63cf2f06e..0871381e8ec0f 100644 --- a/mypy/typeshed/stdlib/dbm/__init__.pyi +++ b/mypy/typeshed/stdlib/dbm/__init__.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import StrOrBytesPath from collections.abc import Iterator, MutableMapping from types import TracebackType -from typing import Literal, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Literal, TypeAlias, type_check_only +from typing_extensions import Self __all__ = ["open", "whichdb", "error"] diff --git a/mypy/typeshed/stdlib/dbm/dumb.pyi b/mypy/typeshed/stdlib/dbm/dumb.pyi index 1c0b7756f2925..d5a769e6a1c25 100644 --- a/mypy/typeshed/stdlib/dbm/dumb.pyi +++ b/mypy/typeshed/stdlib/dbm/dumb.pyi @@ -2,7 +2,8 @@ import sys from _typeshed import StrOrBytesPath from collections.abc import Iterator, MutableMapping from types import TracebackType -from typing_extensions import Self, TypeAlias +from typing import TypeAlias +from typing_extensions import Self __all__ = ["error", "open"] @@ -17,6 +18,9 @@ error = OSError class _Database(MutableMapping[_KeyType, bytes]): def __init__(self, filebasename: str, mode: str, flag: str = "c") -> None: ... def sync(self) -> None: ... + if sys.version_info >= (3, 15): + def reorganize(self) -> None: ... + def iterkeys(self) -> Iterator[bytes]: ... # undocumented def close(self) -> None: ... def __getitem__(self, key: _KeyType) -> bytes: ... diff --git a/mypy/typeshed/stdlib/dbm/sqlite3.pyi b/mypy/typeshed/stdlib/dbm/sqlite3.pyi index e2fba93b20017..e7034cfde50d1 100644 --- a/mypy/typeshed/stdlib/dbm/sqlite3.pyi +++ b/mypy/typeshed/stdlib/dbm/sqlite3.pyi @@ -1,7 +1,8 @@ +import sys from _typeshed import ReadableBuffer, StrOrBytesPath, Unused from collections.abc import Generator, MutableMapping -from typing import Final, Literal -from typing_extensions import LiteralString, Self, TypeAlias +from typing import Final, Literal, TypeAlias +from typing_extensions import LiteralString, Self BUILD_TABLE: Final[LiteralString] GET_SIZE: Final[LiteralString] @@ -9,6 +10,8 @@ LOOKUP_KEY: Final[LiteralString] STORE_KV: Final[LiteralString] DELETE_KEY: Final[LiteralString] ITER_KEYS: Final[LiteralString] +if sys.version_info >= (3, 15): + REORGANIZE: Final[LiteralString] _SqliteData: TypeAlias = str | ReadableBuffer | int | float @@ -25,5 +28,7 @@ class _Database(MutableMapping[bytes, bytes]): def keys(self) -> list[bytes]: ... # type: ignore[override] def __enter__(self) -> Self: ... def __exit__(self, *args: Unused) -> None: ... + if sys.version_info >= (3, 15): + def reorganize(self) -> None: ... def open(filename: StrOrBytesPath, /, flag: Literal["r", "w", "c", "n"] = "r", mode: int = 0o666) -> _Database: ... diff --git a/mypy/typeshed/stdlib/decimal.pyi b/mypy/typeshed/stdlib/decimal.pyi index 2e06c2d1b724a..f16fa8aaae31f 100644 --- a/mypy/typeshed/stdlib/decimal.pyi +++ b/mypy/typeshed/stdlib/decimal.pyi @@ -26,11 +26,13 @@ from _decimal import ( ) from collections.abc import Container, Sequence from types import TracebackType -from typing import Any, ClassVar, Literal, NamedTuple, final, overload, type_check_only -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, ClassVar, Literal, NamedTuple, TypeAlias, final, overload, type_check_only +from typing_extensions import Self, disjoint_base if sys.version_info >= (3, 14): from _decimal import IEEE_CONTEXT_MAX_BITS as IEEE_CONTEXT_MAX_BITS, IEEEContext as IEEEContext +if sys.version_info >= (3, 15): + from _decimal import SPEC_VERSION as SPEC_VERSION _Decimal: TypeAlias = Decimal | int _DecimalNew: TypeAlias = Decimal | float | str | tuple[int, Sequence[int], int] @@ -116,10 +118,12 @@ class Decimal: def imag(self) -> Decimal: ... def conjugate(self) -> Decimal: ... def __complex__(self) -> complex: ... + @overload def __round__(self) -> int: ... @overload def __round__(self, ndigits: int, /) -> Decimal: ... + def __floor__(self) -> int: ... def __ceil__(self) -> int: ... def fma(self, other: _Decimal, third: _Decimal, context: Context | None = None) -> Decimal: ... diff --git a/mypy/typeshed/stdlib/difflib.pyi b/mypy/typeshed/stdlib/difflib.pyi index 6efe68322bb65..a3bda7f9b2d0c 100644 --- a/mypy/typeshed/stdlib/difflib.pyi +++ b/mypy/typeshed/stdlib/difflib.pyi @@ -39,6 +39,7 @@ class SequenceMatcher(Generic[_T]): b: Sequence[str] = "", autojunk: bool = True, ) -> None: ... + def set_seqs(self, a: Sequence[_T], b: Sequence[_T]) -> None: ... def set_seq1(self, a: Sequence[_T]) -> None: ... def set_seq2(self, b: Sequence[_T]) -> None: ... @@ -69,16 +70,33 @@ else: def IS_LINE_JUNK(line: str, pat: Callable[[str], re.Match[str] | None] = ...) -> bool: ... def IS_CHARACTER_JUNK(ch: str, ws: str = " \t") -> bool: ... # ws is undocumented -def unified_diff( - a: Sequence[str], - b: Sequence[str], - fromfile: str = "", - tofile: str = "", - fromfiledate: str = "", - tofiledate: str = "", - n: int = 3, - lineterm: str = "\n", -) -> Iterator[str]: ... + +if sys.version_info >= (3, 15): + def unified_diff( + a: Sequence[str], + b: Sequence[str], + fromfile: str = "", + tofile: str = "", + fromfiledate: str = "", + tofiledate: str = "", + n: int = 3, + lineterm: str = "\n", + *, + color: bool = False, + ) -> Iterator[str]: ... + +else: + def unified_diff( + a: Sequence[str], + b: Sequence[str], + fromfile: str = "", + tofile: str = "", + fromfiledate: str = "", + tofiledate: str = "", + n: int = 3, + lineterm: str = "\n", + ) -> Iterator[str]: ... + def context_diff( a: Sequence[str], b: Sequence[str], diff --git a/mypy/typeshed/stdlib/dis.pyi b/mypy/typeshed/stdlib/dis.pyi index 52794a588ca83..0ad928934671b 100644 --- a/mypy/typeshed/stdlib/dis.pyi +++ b/mypy/typeshed/stdlib/dis.pyi @@ -3,7 +3,7 @@ import types from collections.abc import Callable, Iterator from opcode import * # `dis` re-exports it as a part of public API from typing import IO, Any, Final, NamedTuple, overload -from typing_extensions import Self, TypeAlias, deprecated, disjoint_base +from typing_extensions import Self, deprecated, disjoint_base __all__ = [ "code_info", @@ -41,7 +41,7 @@ else: # Strictly this should not have to include Callable, but mypy doesn't use FunctionType # for functions (python/mypy#3171) -_HaveCodeType: TypeAlias = types.MethodType | types.FunctionType | types.CodeType | type | Callable[..., Any] +_HaveCodeType = types.MethodType | types.FunctionType | types.CodeType | type | Callable[..., Any] if sys.version_info >= (3, 11): class Positions(NamedTuple): diff --git a/mypy/typeshed/stdlib/distutils/archive_util.pyi b/mypy/typeshed/stdlib/distutils/archive_util.pyi index 16684ff069568..5de23bad6bdb3 100644 --- a/mypy/typeshed/stdlib/distutils/archive_util.pyi +++ b/mypy/typeshed/stdlib/distutils/archive_util.pyi @@ -23,6 +23,7 @@ def make_archive( owner: str | None = None, group: str | None = None, ) -> str: ... + def make_tarball( base_name: str, base_dir: StrPath, diff --git a/mypy/typeshed/stdlib/distutils/ccompiler.pyi b/mypy/typeshed/stdlib/distutils/ccompiler.pyi index 5bff209807eef..83bbada6c4c48 100644 --- a/mypy/typeshed/stdlib/distutils/ccompiler.pyi +++ b/mypy/typeshed/stdlib/distutils/ccompiler.pyi @@ -1,8 +1,8 @@ from _typeshed import BytesPath, StrPath, Unused from collections.abc import Callable, Iterable, Sequence from distutils.file_util import _BytesPathT, _StrPathT -from typing import Literal, overload -from typing_extensions import TypeAlias, TypeVarTuple, Unpack +from typing import Literal, TypeAlias, overload +from typing_extensions import TypeVarTuple, Unpack _Macro: TypeAlias = tuple[str] | tuple[str, str | None] _Ts = TypeVarTuple("_Ts") @@ -148,29 +148,35 @@ class CCompiler: extra_preargs: list[str] | None = None, extra_postargs: list[str] | None = None, ) -> None: ... + @overload def executable_filename(self, basename: str, strip_dir: Literal[0, False] = 0, output_dir: StrPath = "") -> str: ... @overload def executable_filename(self, basename: StrPath, strip_dir: Literal[1, True], output_dir: StrPath = "") -> str: ... + def library_filename( self, libname: str, lib_type: str = "static", strip_dir: bool | Literal[0, 1] = 0, output_dir: StrPath = "" ) -> str: ... def object_filenames( self, source_filenames: Iterable[StrPath], strip_dir: bool | Literal[0, 1] = 0, output_dir: StrPath | None = "" ) -> list[str]: ... + @overload def shared_object_filename(self, basename: str, strip_dir: Literal[0, False] = 0, output_dir: StrPath = "") -> str: ... @overload def shared_object_filename(self, basename: StrPath, strip_dir: Literal[1, True], output_dir: StrPath = "") -> str: ... + def execute( self, func: Callable[[Unpack[_Ts]], Unused], args: tuple[Unpack[_Ts]], msg: str | None = None, level: int = 1 ) -> None: ... def spawn(self, cmd: Iterable[str]) -> None: ... def mkpath(self, name: str, mode: int = 0o777) -> None: ... + @overload def move_file(self, src: StrPath, dst: _StrPathT) -> _StrPathT | str: ... @overload def move_file(self, src: BytesPath, dst: _BytesPathT) -> _BytesPathT | bytes: ... + def announce(self, msg: str, level: int = 1) -> None: ... def warn(self, msg: str) -> None: ... def debug_print(self, msg: str) -> None: ... diff --git a/mypy/typeshed/stdlib/distutils/cmd.pyi b/mypy/typeshed/stdlib/distutils/cmd.pyi index 7f97bc3a2c9e0..35b991aba0883 100644 --- a/mypy/typeshed/stdlib/distutils/cmd.pyi +++ b/mypy/typeshed/stdlib/distutils/cmd.pyi @@ -49,6 +49,7 @@ class Command: def ensure_dirname(self, option: str) -> None: ... def get_command_name(self) -> str: ... def set_undefined_options(self, src_cmd: str, *option_pairs: tuple[str, str]) -> None: ... + # NOTE: This list comes directly from the distutils/command folder. Minus bdist_msi and bdist_wininst. @overload def get_finalized_command(self, command: Literal["bdist"], create: bool | Literal[0, 1] = 1) -> bdist: ... @@ -94,6 +95,7 @@ class Command: def get_finalized_command(self, command: Literal["upload"], create: bool | Literal[0, 1] = 1) -> upload: ... @overload def get_finalized_command(self, command: str, create: bool | Literal[0, 1] = 1) -> Command: ... + @overload def reinitialize_command(self, command: Literal["bdist"], reinit_subcommands: bool | Literal[0, 1] = 0) -> bdist: ... @overload @@ -154,6 +156,7 @@ class Command: def reinitialize_command(self, command: str, reinit_subcommands: bool | Literal[0, 1] = 0) -> Command: ... @overload def reinitialize_command(self, command: _CommandT, reinit_subcommands: bool | Literal[0, 1] = 0) -> _CommandT: ... + def run_command(self, command: str) -> None: ... def get_sub_commands(self) -> list[str]: ... def warn(self, msg: str) -> None: ... @@ -161,6 +164,7 @@ class Command: self, func: Callable[[Unpack[_Ts]], Unused], args: tuple[Unpack[_Ts]], msg: str | None = None, level: int = 1 ) -> None: ... def mkpath(self, name: str, mode: int = 0o777) -> None: ... + @overload def copy_file( self, @@ -181,6 +185,7 @@ class Command: link: str | None = None, level: Unused = 1, ) -> tuple[_BytesPathT | bytes, bool]: ... + def copy_tree( self, infile: StrPath, @@ -190,11 +195,14 @@ class Command: preserve_symlinks: bool | Literal[0, 1] = 0, level: Unused = 1, ) -> list[str]: ... + @overload def move_file(self, src: StrPath, dst: _StrPathT, level: Unused = 1) -> _StrPathT | str: ... @overload def move_file(self, src: BytesPath, dst: _BytesPathT, level: Unused = 1) -> _BytesPathT | bytes: ... + def spawn(self, cmd: Iterable[str], search_path: bool | Literal[0, 1] = 1, level: Unused = 1) -> None: ... + @overload def make_archive( self, @@ -215,6 +223,7 @@ class Command: owner: str | None = None, group: str | None = None, ) -> str: ... + def make_file( self, infiles: str | list[str] | tuple[str, ...], diff --git a/mypy/typeshed/stdlib/distutils/command/__init__.pyi b/mypy/typeshed/stdlib/distutils/command/__init__.pyi index 4d7372858af34..856c5eb8b44ad 100644 --- a/mypy/typeshed/stdlib/distutils/command/__init__.pyi +++ b/mypy/typeshed/stdlib/distutils/command/__init__.pyi @@ -1,5 +1,3 @@ -import sys - from . import ( bdist, bdist_dumb, @@ -41,8 +39,3 @@ __all__ = [ "check", "upload", ] - -if sys.version_info < (3, 10): - from . import bdist_wininst - - __all__ += ["bdist_wininst"] diff --git a/mypy/typeshed/stdlib/distutils/command/bdist_wininst.pyi b/mypy/typeshed/stdlib/distutils/command/bdist_wininst.pyi deleted file mode 100644 index cf333bc5400dd..0000000000000 --- a/mypy/typeshed/stdlib/distutils/command/bdist_wininst.pyi +++ /dev/null @@ -1,16 +0,0 @@ -from _typeshed import StrOrBytesPath -from distutils.cmd import Command -from typing import ClassVar - -class bdist_wininst(Command): - description: ClassVar[str] - user_options: ClassVar[list[tuple[str, str | None, str]]] - boolean_options: ClassVar[list[str]] - - def initialize_options(self) -> None: ... - def finalize_options(self) -> None: ... - def run(self) -> None: ... - def get_inidata(self) -> str: ... - def create_exe(self, arcname: StrOrBytesPath, fullname: str, bitmap: StrOrBytesPath | None = None) -> None: ... - def get_installer_filename(self, fullname: str) -> str: ... - def get_exe_bytes(self) -> bytes: ... diff --git a/mypy/typeshed/stdlib/distutils/command/check.pyi b/mypy/typeshed/stdlib/distutils/command/check.pyi index 2c807fd2c4396..f2034e6555fcc 100644 --- a/mypy/typeshed/stdlib/distutils/command/check.pyi +++ b/mypy/typeshed/stdlib/distutils/command/check.pyi @@ -1,6 +1,5 @@ from _typeshed import Incomplete -from typing import Any, ClassVar, Final, Literal -from typing_extensions import TypeAlias +from typing import Any, ClassVar, Final, Literal, TypeAlias from ..cmd import Command diff --git a/mypy/typeshed/stdlib/distutils/command/install.pyi b/mypy/typeshed/stdlib/distutils/command/install.pyi index 1714e01a2c284..7e11cf257c2c3 100644 --- a/mypy/typeshed/stdlib/distutils/command/install.pyi +++ b/mypy/typeshed/stdlib/distutils/command/install.pyi @@ -1,4 +1,3 @@ -import sys from _typeshed import Incomplete from collections.abc import Callable from typing import Any, ClassVar, Final, Literal @@ -10,9 +9,6 @@ HAS_USER_SITE: Final[bool] SCHEME_KEYS: Final[tuple[Literal["purelib"], Literal["platlib"], Literal["headers"], Literal["scripts"], Literal["data"]]] INSTALL_SCHEMES: Final[dict[str, dict[str, str]]] -if sys.version_info < (3, 10): - WINDOWS_SCHEME: Final[dict[str, str]] - class install(Command): description: str user_options: ClassVar[list[tuple[str, str | None, str]]] diff --git a/mypy/typeshed/stdlib/distutils/dist.pyi b/mypy/typeshed/stdlib/distutils/dist.pyi index 412b94131b54e..58650e853cce6 100644 --- a/mypy/typeshed/stdlib/distutils/dist.pyi +++ b/mypy/typeshed/stdlib/distutils/dist.pyi @@ -22,8 +22,7 @@ from distutils.command.register import register from distutils.command.sdist import sdist from distutils.command.upload import upload from re import Pattern -from typing import IO, ClassVar, Literal, TypeVar, overload -from typing_extensions import TypeAlias +from typing import IO, ClassVar, Literal, TypeAlias, TypeVar, overload command_re: Pattern[str] @@ -122,6 +121,7 @@ class Distribution: def print_commands(self) -> None: ... def get_command_list(self): ... def get_command_packages(self): ... + # NOTE: This list comes directly from the distutils/command folder. Minus bdist_msi and bdist_wininst. @overload def get_command_obj(self, command: Literal["bdist"], create: Literal[1, True] = 1) -> bdist: ... @@ -168,6 +168,7 @@ class Distribution: # Not replicating the overloads for "Command | None", user may use "isinstance" @overload def get_command_obj(self, command: str, create: Literal[0, False]) -> Command | None: ... + @overload def get_command_class(self, command: Literal["bdist"]) -> type[bdist]: ... @overload @@ -210,6 +211,7 @@ class Distribution: def get_command_class(self, command: Literal["upload"]) -> type[upload]: ... @overload def get_command_class(self, command: str) -> type[Command]: ... + @overload def reinitialize_command(self, command: Literal["bdist"], reinit_subcommands: bool = False) -> bdist: ... @overload @@ -256,6 +258,7 @@ class Distribution: def reinitialize_command(self, command: str, reinit_subcommands: bool = False) -> Command: ... @overload def reinitialize_command(self, command: _CommandT, reinit_subcommands: bool = False) -> _CommandT: ... + def announce(self, msg, level: int = 2) -> None: ... def run_commands(self) -> None: ... def run_command(self, command: str) -> None: ... diff --git a/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi b/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi index f3fa2a1255a6d..676ce1bea313e 100644 --- a/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi +++ b/mypy/typeshed/stdlib/distutils/fancy_getopt.pyi @@ -1,8 +1,7 @@ from collections.abc import Iterable, Mapping from getopt import _SliceableT, _StrSequenceT_co from re import Pattern -from typing import Any, Final, overload -from typing_extensions import TypeAlias +from typing import Any, Final, TypeAlias, overload _Option: TypeAlias = tuple[str, str | None, str] @@ -13,6 +12,7 @@ longopt_xlate: Final[dict[int, int]] class FancyGetopt: def __init__(self, option_table: list[_Option] | None = None) -> None: ... + # TODO: kinda wrong, `getopt(object=object())` is invalid @overload def getopt( @@ -22,6 +22,7 @@ class FancyGetopt: def getopt( self, args: _SliceableT[_StrSequenceT_co] | None, object: Any ) -> _StrSequenceT_co: ... # object is an arbitrary non-slotted object + def get_option_order(self) -> list[tuple[str, str]]: ... def generate_help(self, header: str | None = None) -> list[str]: ... diff --git a/mypy/typeshed/stdlib/distutils/file_util.pyi b/mypy/typeshed/stdlib/distutils/file_util.pyi index c763f91a958d7..9d5bf5080b058 100644 --- a/mypy/typeshed/stdlib/distutils/file_util.pyi +++ b/mypy/typeshed/stdlib/distutils/file_util.pyi @@ -27,6 +27,7 @@ def copy_file( verbose: bool | Literal[0, 1] = 1, dry_run: bool | Literal[0, 1] = 0, ) -> tuple[_BytesPathT | bytes, bool]: ... + @overload def move_file( src: StrPath, dst: _StrPathT, verbose: bool | Literal[0, 1] = 1, dry_run: bool | Literal[0, 1] = 0 @@ -35,4 +36,5 @@ def move_file( def move_file( src: BytesPath, dst: _BytesPathT, verbose: bool | Literal[0, 1] = 1, dry_run: bool | Literal[0, 1] = 0 ) -> _BytesPathT | bytes: ... + def write_file(filename: StrOrBytesPath, contents: Iterable[str]) -> None: ... diff --git a/mypy/typeshed/stdlib/distutils/filelist.pyi b/mypy/typeshed/stdlib/distutils/filelist.pyi index 607a78a1fbaca..c3347fe7d1d20 100644 --- a/mypy/typeshed/stdlib/distutils/filelist.pyi +++ b/mypy/typeshed/stdlib/distutils/filelist.pyi @@ -15,6 +15,7 @@ class FileList: def sort(self) -> None: ... def remove_duplicates(self) -> None: ... def process_template_line(self, line: str) -> None: ... + @overload def include_pattern( self, pattern: str, anchor: bool | Literal[0, 1] = 1, prefix: str | None = None, is_regex: Literal[0, False] = 0 @@ -29,6 +30,7 @@ class FileList: prefix: str | None = None, is_regex: bool | Literal[0, 1] = 0, ) -> bool: ... + @overload def exclude_pattern( self, pattern: str, anchor: bool | Literal[0, 1] = 1, prefix: str | None = None, is_regex: Literal[0, False] = 0 @@ -46,6 +48,7 @@ class FileList: def findall(dir: str = ".") -> list[str]: ... def glob_to_re(pattern: str) -> str: ... + @overload def translate_pattern( pattern: str, anchor: bool | Literal[0, 1] = 1, prefix: str | None = None, is_regex: Literal[False, 0] = 0 diff --git a/mypy/typeshed/stdlib/distutils/sysconfig.pyi b/mypy/typeshed/stdlib/distutils/sysconfig.pyi index 4a9c45eb562a4..7c8e0e7b149e0 100644 --- a/mypy/typeshed/stdlib/distutils/sysconfig.pyi +++ b/mypy/typeshed/stdlib/distutils/sysconfig.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Mapping from distutils.ccompiler import CCompiler from typing import Final, Literal, overload @@ -12,15 +11,18 @@ project_base: Final[str] python_build: Final[bool] def expand_makefile_vars(s: str, vars: Mapping[str, str]) -> str: ... + @overload @deprecated("SO is deprecated, use EXT_SUFFIX. Support is removed in Python 3.11") def get_config_var(name: Literal["SO"]) -> int | str | None: ... @overload def get_config_var(name: str) -> int | str | None: ... + @overload def get_config_vars() -> dict[str, str | int]: ... @overload def get_config_vars(arg: str, /, *args: str) -> list[str | int]: ... + def get_config_h_filename() -> str: ... def get_makefile_filename() -> str: ... def get_python_inc(plat_specific: bool | Literal[0, 1] = 0, prefix: str | None = None) -> str: ... @@ -28,6 +30,3 @@ def get_python_lib( plat_specific: bool | Literal[0, 1] = 0, standard_lib: bool | Literal[0, 1] = 0, prefix: str | None = None ) -> str: ... def customize_compiler(compiler: CCompiler) -> None: ... - -if sys.version_info < (3, 10): - def get_python_version() -> str: ... diff --git a/mypy/typeshed/stdlib/doctest.pyi b/mypy/typeshed/stdlib/doctest.pyi index 1bb96e1a77868..32c68e3f2c7b3 100644 --- a/mypy/typeshed/stdlib/doctest.pyi +++ b/mypy/typeshed/stdlib/doctest.pyi @@ -3,8 +3,8 @@ import types import unittest from _typeshed import ExcInfo from collections.abc import Callable -from typing import Any, Final, NamedTuple, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, Final, NamedTuple, TypeAlias, type_check_only +from typing_extensions import Self __all__ = [ "register_optionflag", diff --git a/mypy/typeshed/stdlib/email/__init__.pyi b/mypy/typeshed/stdlib/email/__init__.pyi index 53f8c350b01e3..aabdca32d506d 100644 --- a/mypy/typeshed/stdlib/email/__init__.pyi +++ b/mypy/typeshed/stdlib/email/__init__.pyi @@ -2,8 +2,7 @@ from collections.abc import Callable from email._policybase import _MessageT from email.message import Message from email.policy import Policy -from typing import IO, overload -from typing_extensions import TypeAlias +from typing import IO, TypeAlias, overload # At runtime, listing submodules in __all__ without them being imported is # valid, and causes them to be included in a star import. See #6523 @@ -38,6 +37,7 @@ def message_from_string(s: str) -> Message: ... def message_from_string(s: str, _class: Callable[[], _MessageT]) -> _MessageT: ... @overload def message_from_string(s: str, _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]) -> _MessageT: ... + @overload def message_from_bytes(s: bytes | bytearray) -> Message: ... @overload @@ -46,12 +46,14 @@ def message_from_bytes(s: bytes | bytearray, _class: Callable[[], _MessageT]) -> def message_from_bytes( s: bytes | bytearray, _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT] ) -> _MessageT: ... + @overload def message_from_file(fp: IO[str]) -> Message: ... @overload def message_from_file(fp: IO[str], _class: Callable[[], _MessageT]) -> _MessageT: ... @overload def message_from_file(fp: IO[str], _class: Callable[[], _MessageT] = ..., *, policy: Policy[_MessageT]) -> _MessageT: ... + @overload def message_from_binary_file(fp: IO[bytes]) -> Message: ... @overload diff --git a/mypy/typeshed/stdlib/email/_header_value_parser.pyi b/mypy/typeshed/stdlib/email/_header_value_parser.pyi index a6d7c48d69cd8..e75e7ba1cf06c 100644 --- a/mypy/typeshed/stdlib/email/_header_value_parser.pyi +++ b/mypy/typeshed/stdlib/email/_header_value_parser.pyi @@ -17,18 +17,17 @@ TOKEN_ENDS: Final[set[str]] ASPECIALS: Final[set[str]] ATTRIBUTE_ENDS: Final[set[str]] EXTENDED_ATTRIBUTE_ENDS: Final[set[str]] -# Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 +# Added in Python 3.10.15, 3.11.10, 3.12.5 NLSET: Final[set[str]] -# Added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 +# Added in Python 3.10.15, 3.11.10, 3.12.5 SPECIALSNL: Final[set[str]] -# Added in Python 3.9.23, 3.10.17, 3.11.12, 3.12.9, 3.13.2 +# Added in Python 3.10.17, 3.11.12, 3.12.9, 3.13.2 def make_quoted_pairs(value: Any) -> str: ... def quote_string(value: Any) -> str: ... -if sys.version_info >= (3, 10): - # Added in Python 3.10.20, 3.11.15, 3.12.13, 3.13.12, 3.14.3 - def make_parenthesis_pairs(value: Any) -> str: ... +# Added in Python 3.10.20, 3.11.15, 3.12.13, 3.13.12, 3.14.3 +def make_parenthesis_pairs(value: Any) -> str: ... rfc2047_matcher: Final[Pattern[str]] diff --git a/mypy/typeshed/stdlib/email/charset.pyi b/mypy/typeshed/stdlib/email/charset.pyi index e1930835bbd11..353cdeb0b9ddd 100644 --- a/mypy/typeshed/stdlib/email/charset.pyi +++ b/mypy/typeshed/stdlib/email/charset.pyi @@ -27,10 +27,12 @@ class Charset: def get_output_charset(self) -> str | None: ... def header_encode(self, string: str) -> str: ... def header_encode_lines(self, string: str, maxlengths: Iterator[int]) -> list[str | None]: ... + @overload def body_encode(self, string: None) -> None: ... @overload def body_encode(self, string: str | bytes) -> str: ... + __hash__: ClassVar[None] # type: ignore[assignment] def __eq__(self, other: object) -> bool: ... def __ne__(self, value: object, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/email/errors.pyi b/mypy/typeshed/stdlib/email/errors.pyi index b501a58665560..4da60250965e5 100644 --- a/mypy/typeshed/stdlib/email/errors.pyi +++ b/mypy/typeshed/stdlib/email/errors.pyi @@ -1,5 +1,3 @@ -import sys - class MessageError(Exception): ... class MessageParseError(MessageError): ... class HeaderParseError(MessageParseError): ... @@ -37,6 +35,4 @@ class NonPrintableDefect(HeaderDefect): class ObsoleteHeaderDefect(HeaderDefect): ... class NonASCIILocalPartDefect(HeaderDefect): ... - -if sys.version_info >= (3, 10): - class InvalidDateDefect(HeaderDefect): ... +class InvalidDateDefect(HeaderDefect): ... diff --git a/mypy/typeshed/stdlib/email/feedparser.pyi b/mypy/typeshed/stdlib/email/feedparser.pyi index d9279e9cd996d..ec92eef678861 100644 --- a/mypy/typeshed/stdlib/email/feedparser.pyi +++ b/mypy/typeshed/stdlib/email/feedparser.pyi @@ -11,6 +11,7 @@ class FeedParser(Generic[_MessageT]): def __init__(self: FeedParser[Message], _factory: None = None, *, policy: Policy[Message] = ...) -> None: ... @overload def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ... + def feed(self, data: str) -> None: ... def close(self) -> _MessageT: ... @@ -19,4 +20,5 @@ class BytesFeedParser(FeedParser[_MessageT]): def __init__(self: BytesFeedParser[Message], _factory: None = None, *, policy: Policy[Message] = ...) -> None: ... @overload def __init__(self, _factory: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ... + def feed(self, data: bytes | bytearray) -> None: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/email/generator.pyi b/mypy/typeshed/stdlib/email/generator.pyi index d30e686299fab..c2a9bb0921d78 100644 --- a/mypy/typeshed/stdlib/email/generator.pyi +++ b/mypy/typeshed/stdlib/email/generator.pyi @@ -12,6 +12,7 @@ _MessageT = TypeVar("_MessageT", bound=Message[Any, Any], default=Any) class Generator(Generic[_MessageT]): maxheaderlen: int | None policy: Policy[_MessageT] | None + @overload def __init__( self: Generator[Any], # The Policy of the message is used. @@ -30,6 +31,7 @@ class Generator(Generic[_MessageT]): *, policy: Policy[_MessageT], ) -> None: ... + def write(self, s: str) -> None: ... def flatten(self, msg: _MessageT, unixfrom: bool = False, linesep: str | None = None) -> None: ... def clone(self, fp: SupportsWrite[str]) -> Self: ... diff --git a/mypy/typeshed/stdlib/email/iterators.pyi b/mypy/typeshed/stdlib/email/iterators.pyi index d964d68438336..b9a8de835401e 100644 --- a/mypy/typeshed/stdlib/email/iterators.pyi +++ b/mypy/typeshed/stdlib/email/iterators.pyi @@ -1,11 +1,14 @@ from _typeshed import SupportsWrite from collections.abc import Iterator from email.message import Message +from typing import TypeVar + +_T = TypeVar("_T", bound=Message) __all__ = ["body_line_iterator", "typed_subpart_iterator", "walk"] def body_line_iterator(msg: Message, decode: bool = False) -> Iterator[str]: ... -def typed_subpart_iterator(msg: Message, maintype: str = "text", subtype: str | None = None) -> Iterator[str]: ... +def typed_subpart_iterator(msg: _T, maintype: str = "text", subtype: str | None = None) -> Iterator[_T]: ... def walk(self: Message) -> Iterator[Message]: ... # We include the seemingly private function because it is documented in the stdlib documentation. diff --git a/mypy/typeshed/stdlib/email/message.pyi b/mypy/typeshed/stdlib/email/message.pyi index 08ba88b4ee6da..784c2cace4255 100644 --- a/mypy/typeshed/stdlib/email/message.pyi +++ b/mypy/typeshed/stdlib/email/message.pyi @@ -5,8 +5,8 @@ from email.charset import Charset from email.contentmanager import ContentManager from email.errors import MessageDefect from email.policy import Policy -from typing import Any, Generic, Literal, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, Generic, Literal, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self __all__ = ["Message", "EmailMessage"] @@ -44,6 +44,7 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): def set_unixfrom(self, unixfrom: str) -> None: ... def get_unixfrom(self) -> str | None: ... def attach(self, payload: _PayloadType) -> None: ... + # `i: int` without a multipart payload results in an error # `| MaybeNone` acts like `| Any`: can be None for cleared or unset payload, but annoying to check @overload # multipart @@ -56,6 +57,7 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | MaybeNone: ... @overload # not multipart, IDEM but w/o kwarg def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | MaybeNone: ... + # If `charset=None` and payload supports both `encode` AND `decode`, # then an invalid payload could be passed, but this is unlikely # Not[_SupportsEncodeToPayload] @@ -69,6 +71,7 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): payload: _SupportsEncodeToPayload | _SupportsDecodeToPayload | _PayloadType | _MultipartPayloadType, charset: Charset | str, ) -> None: ... + def set_charset(self, charset: _CharsetType) -> None: ... def get_charset(self) -> _CharsetType: ... def __len__(self) -> int: ... @@ -84,14 +87,17 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): def keys(self) -> list[str]: ... def values(self) -> list[_HeaderT_co]: ... def items(self) -> list[tuple[str, _HeaderT_co]]: ... + @overload def get(self, name: str, failobj: None = None) -> _HeaderT_co | None: ... @overload def get(self, name: str, failobj: _T) -> _HeaderT_co | _T: ... + @overload def get_all(self, name: str, failobj: None = None) -> list[_HeaderT_co] | None: ... @overload def get_all(self, name: str, failobj: _T) -> list[_HeaderT_co] | _T: ... + def add_header(self, _name: str, _value: str, **_params: _ParamsType) -> None: ... def replace_header(self, _name: str, _value: _HeaderParamT_contra) -> None: ... def get_content_type(self) -> str: ... @@ -99,37 +105,46 @@ class Message(Generic[_HeaderT_co, _HeaderParamT_contra]): def get_content_subtype(self) -> str: ... def get_default_type(self) -> str: ... def set_default_type(self, ctype: str) -> None: ... + @overload def get_params( self, failobj: None = None, header: str = "content-type", unquote: bool = True ) -> list[tuple[str, str]] | None: ... @overload def get_params(self, failobj: _T, header: str = "content-type", unquote: bool = True) -> list[tuple[str, str]] | _T: ... + @overload def get_param( self, param: str, failobj: None = None, header: str = "content-type", unquote: bool = True ) -> _ParamType | None: ... @overload def get_param(self, param: str, failobj: _T, header: str = "content-type", unquote: bool = True) -> _ParamType | _T: ... + def del_param(self, param: str, header: str = "content-type", requote: bool = True) -> None: ... def set_type(self, type: str, header: str = "Content-Type", requote: bool = True) -> None: ... + @overload def get_filename(self, failobj: None = None) -> str | None: ... @overload def get_filename(self, failobj: _T) -> str | _T: ... + @overload def get_boundary(self, failobj: None = None) -> str | None: ... @overload def get_boundary(self, failobj: _T) -> str | _T: ... + def set_boundary(self, boundary: str) -> None: ... + @overload def get_content_charset(self) -> str | None: ... @overload def get_content_charset(self, failobj: _T) -> str | _T: ... + @overload def get_charsets(self, failobj: None = None) -> list[str | None]: ... @overload def get_charsets(self, failobj: _T) -> list[str | _T]: ... + def walk(self) -> Generator[Self]: ... def get_content_disposition(self) -> str | None: ... def as_string(self, unixfrom: bool = False, maxheaderlen: int = 0, policy: Policy[Any] | None = None) -> str: ... diff --git a/mypy/typeshed/stdlib/email/parser.pyi b/mypy/typeshed/stdlib/email/parser.pyi index a4924a6cbd88f..f1b418ee30a02 100644 --- a/mypy/typeshed/stdlib/email/parser.pyi +++ b/mypy/typeshed/stdlib/email/parser.pyi @@ -16,6 +16,7 @@ class Parser(Generic[_MessageT]): def __init__(self, _class: None = None, *, policy: Policy[_MessageT]) -> None: ... @overload def __init__(self, _class: Callable[[], _MessageT] | None, *, policy: Policy[_MessageT] = ...) -> None: ... + def parse(self, fp: SupportsRead[str], headersonly: bool = False) -> _MessageT: ... def parsestr(self, text: str, headersonly: bool = False) -> _MessageT: ... @@ -25,12 +26,14 @@ class HeaderParser(Parser[_MessageT]): class BytesParser(Generic[_MessageT]): parser: Parser[_MessageT] + @overload def __init__(self: BytesParser[Message[str, str]], _class: None = None) -> None: ... @overload def __init__(self, _class: None = None, *, policy: Policy[_MessageT]) -> None: ... @overload def __init__(self, _class: Callable[[], _MessageT], *, policy: Policy[_MessageT] = ...) -> None: ... + def parse(self, fp: _WrappedBuffer, headersonly: bool = False) -> _MessageT: ... def parsebytes(self, text: bytes | bytearray, headersonly: bool = False) -> _MessageT: ... diff --git a/mypy/typeshed/stdlib/email/policy.pyi b/mypy/typeshed/stdlib/email/policy.pyi index 35c999919eede..6b719f3c93fa0 100644 --- a/mypy/typeshed/stdlib/email/policy.pyi +++ b/mypy/typeshed/stdlib/email/policy.pyi @@ -12,6 +12,7 @@ class EmailPolicy(Policy[_MessageT]): refold_source: str header_factory: Callable[[str, Any], Any] content_manager: ContentManager + @overload def __init__( self: EmailPolicy[EmailMessage], @@ -46,6 +47,7 @@ class EmailPolicy(Policy[_MessageT]): header_factory: Callable[[str, str], str] = ..., content_manager: ContentManager = ..., ) -> None: ... + def header_source_parse(self, sourcelines: list[str]) -> tuple[str, str]: ... def header_store_parse(self, name: str, value: Any) -> tuple[str, Any]: ... def header_fetch_parse(self, name: str, value: str) -> Any: ... diff --git a/mypy/typeshed/stdlib/email/utils.pyi b/mypy/typeshed/stdlib/email/utils.pyi index cece2f2a11190..6b47950a2ef8f 100644 --- a/mypy/typeshed/stdlib/email/utils.pyi +++ b/mypy/typeshed/stdlib/email/utils.pyi @@ -4,8 +4,8 @@ from _typeshed import Unused from collections.abc import Iterable from email import _ParamType from email.charset import Charset -from typing import overload -from typing_extensions import TypeAlias, deprecated +from typing import TypeAlias, overload +from typing_extensions import deprecated __all__ = [ "collapse_rfc2231_value", @@ -36,14 +36,17 @@ def formataddr(pair: tuple[str | None, str], charset: str | Charset = "utf-8") - # `strict` parameter added in Python 3.9.20, 3.10.15, 3.11.10, 3.12.5 def getaddresses(fieldvalues: Iterable[str], *, strict: bool = True) -> list[tuple[str, str]]: ... + @overload def parsedate(data: None) -> None: ... @overload def parsedate(data: str) -> tuple[int, int, int, int, int, int, int, int, int] | None: ... + @overload def parsedate_tz(data: None) -> None: ... @overload def parsedate_tz(data: str) -> _PDTZ | None: ... + def parsedate_to_datetime(data: str) -> datetime.datetime: ... def mktime_tz(data: _PDTZ) -> int: ... def formatdate(timeval: float | None = None, localtime: bool = False, usegmt: bool = False) -> str: ... diff --git a/mypy/typeshed/stdlib/enum.pyi b/mypy/typeshed/stdlib/enum.pyi index f9b53e1d45f16..1d216b90b6b7b 100644 --- a/mypy/typeshed/stdlib/enum.pyi +++ b/mypy/typeshed/stdlib/enum.pyi @@ -4,8 +4,8 @@ import types from _typeshed import SupportsKeysAndGetItem, Unused from builtins import property as _builtins_property from collections.abc import Callable, Iterable, Iterator, Mapping -from typing import Any, Final, Generic, Literal, SupportsIndex, TypeVar, overload -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, Final, Generic, Literal, SupportsIndex, TypeAlias, TypeVar, overload +from typing_extensions import Self, disjoint_base __all__ = ["EnumMeta", "Enum", "IntEnum", "Flag", "IntFlag", "auto", "unique"] @@ -37,6 +37,8 @@ if sys.version_info >= (3, 11): if sys.version_info >= (3, 13): __all__ += ["EnumDict"] +if sys.version_info >= (3, 15): + __all__ += ["show_flag_values", "bin"] _EnumMemberT = TypeVar("_EnumMemberT") _EnumerationT = TypeVar("_EnumerationT", bound=type[Enum]) @@ -81,6 +83,7 @@ class _EnumDict(dict[str, Any]): def update(self, members: SupportsKeysAndGetItem[str, Any], **more_members: Any) -> None: ... @overload def update(self, members: Iterable[tuple[str, Any]], **more_members: Any) -> None: ... + if sys.version_info >= (3, 13): @property def member_names(self) -> list[str]: ... @@ -114,10 +117,8 @@ class EnumMeta(type): def __contains__(self: type[Any], value: object) -> bool: ... elif sys.version_info >= (3, 11): def __contains__(self: type[Any], member: object) -> bool: ... - elif sys.version_info >= (3, 10): - def __contains__(self: type[Any], obj: object) -> bool: ... else: - def __contains__(self: type[Any], member: object) -> bool: ... + def __contains__(self: type[Any], obj: object) -> bool: ... def __getitem__(self: type[_EnumMemberT], name: str) -> _EnumMemberT: ... @_builtins_property diff --git a/mypy/typeshed/stdlib/faulthandler.pyi b/mypy/typeshed/stdlib/faulthandler.pyi index 17d4eef69af76..6999933c43b98 100644 --- a/mypy/typeshed/stdlib/faulthandler.pyi +++ b/mypy/typeshed/stdlib/faulthandler.pyi @@ -3,16 +3,39 @@ from _typeshed import FileDescriptorLike def cancel_dump_traceback_later() -> None: ... def disable() -> None: ... -def dump_traceback(file: FileDescriptorLike = sys.stderr, all_threads: bool = True) -> None: ... + +if sys.version_info >= (3, 15): + def dump_traceback( + file: FileDescriptorLike = sys.stderr, all_threads: bool = True, *, max_threads: int | None = None + ) -> None: ... + +else: + def dump_traceback(file: FileDescriptorLike = sys.stderr, all_threads: bool = True) -> None: ... if sys.version_info >= (3, 14): def dump_c_stack(file: FileDescriptorLike = sys.stderr) -> None: ... -def dump_traceback_later( - timeout: float, repeat: bool = False, file: FileDescriptorLike = sys.stderr, exit: bool = False -) -> None: ... +if sys.version_info >= (3, 15): + def dump_traceback_later( + timeout: float, + repeat: bool = False, + file: FileDescriptorLike = sys.stderr, + exit: bool = False, + *, + max_threads: int | None = None, + ) -> None: ... -if sys.version_info >= (3, 14): +else: + def dump_traceback_later( + timeout: float, repeat: bool = False, file: FileDescriptorLike = sys.stderr, exit: bool = False + ) -> None: ... + +if sys.version_info >= (3, 15): + def enable( + file: FileDescriptorLike = sys.stderr, all_threads: bool = True, c_stack: bool = True, *, max_threads: int | None = None + ) -> None: ... + +elif sys.version_info >= (3, 14): def enable(file: FileDescriptorLike = sys.stderr, all_threads: bool = True, c_stack: bool = True) -> None: ... else: @@ -21,5 +44,18 @@ else: def is_enabled() -> bool: ... if sys.platform != "win32": - def register(signum: int, file: FileDescriptorLike = sys.stderr, all_threads: bool = True, chain: bool = False) -> None: ... + if sys.version_info >= (3, 15): + def register( + signum: int, + file: FileDescriptorLike = sys.stderr, + all_threads: bool = True, + chain: bool = False, + *, + max_threads: int | None = None, + ) -> None: ... + else: + def register( + signum: int, file: FileDescriptorLike = sys.stderr, all_threads: bool = True, chain: bool = False + ) -> None: ... + def unregister(signum: int, /) -> None: ... diff --git a/mypy/typeshed/stdlib/fcntl.pyi b/mypy/typeshed/stdlib/fcntl.pyi index 5a3e89b0c6766..c17f31c4bebe8 100644 --- a/mypy/typeshed/stdlib/fcntl.pyi +++ b/mypy/typeshed/stdlib/fcntl.pyi @@ -45,11 +45,8 @@ if sys.platform != "win32": F_OFD_GETLK: Final[int] F_OFD_SETLK: Final[int] F_OFD_SETLKW: Final[int] - - if sys.version_info >= (3, 10): - F_GETPIPE_SZ: Final[int] - F_SETPIPE_SZ: Final[int] - + F_GETPIPE_SZ: Final[int] + F_SETPIPE_SZ: Final[int] DN_ACCESS: Final[int] DN_ATTRIB: Final[int] DN_CREATE: Final[int] @@ -140,6 +137,7 @@ if sys.platform != "win32": def fcntl(fd: FileDescriptorLike, cmd: int, arg: int = 0, /) -> int: ... @overload def fcntl(fd: FileDescriptorLike, cmd: int, arg: str | ReadOnlyBuffer, /) -> bytes: ... + # If arg is an int, return int @overload def ioctl(fd: FileDescriptorLike, request: int, arg: int = 0, mutate_flag: bool = True, /) -> int: ... @@ -154,5 +152,6 @@ if sys.platform != "win32": def ioctl(fd: FileDescriptorLike, request: int, arg: WriteableBuffer, mutate_flag: Literal[False], /) -> bytes: ... @overload def ioctl(fd: FileDescriptorLike, request: int, arg: Buffer, mutate_flag: bool = True, /) -> Any: ... + def flock(fd: FileDescriptorLike, operation: int, /) -> None: ... def lockf(fd: FileDescriptorLike, cmd: int, len: int = 0, start: int = 0, whence: int = 0, /) -> Any: ... diff --git a/mypy/typeshed/stdlib/fileinput.pyi b/mypy/typeshed/stdlib/fileinput.pyi index 95164de2f0107..db9c228f5f28e 100644 --- a/mypy/typeshed/stdlib/fileinput.pyi +++ b/mypy/typeshed/stdlib/fileinput.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import AnyStr_co, StrOrBytesPath from collections.abc import Callable, Iterable, Iterator from types import GenericAlias, TracebackType -from typing import IO, Any, AnyStr, Literal, Protocol, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import IO, Any, AnyStr, Literal, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self, deprecated __all__ = [ "input", @@ -30,10 +30,55 @@ class _HasReadlineAndFileno(Protocol[AnyStr_co]): def readline(self) -> AnyStr_co: ... def fileno(self) -> int: ... -if sys.version_info >= (3, 10): +# encoding and errors are added +@overload +def input( + files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, + inplace: bool = False, + backup: str = "", + *, + mode: _TextMode = "r", + openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, + encoding: str | None = None, + errors: str | None = None, +) -> FileInput[str]: ... +@overload +def input( + files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, + inplace: bool = False, + backup: str = "", + *, + mode: Literal["rb"], + openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, + encoding: None = None, + errors: None = None, +) -> FileInput[bytes]: ... +@overload +def input( + files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, + inplace: bool = False, + backup: str = "", + *, + mode: str, + openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, + encoding: str | None = None, + errors: str | None = None, +) -> FileInput[Any]: ... + +def close() -> None: ... +def nextfile() -> None: ... +def filename() -> str: ... +def lineno() -> int: ... +def filelineno() -> int: ... +def fileno() -> int: ... +def isfirstline() -> bool: ... +def isstdin() -> bool: ... + +class FileInput(Iterator[AnyStr]): # encoding and errors are added @overload - def input( + def __init__( + self: FileInput[str], files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, inplace: bool = False, backup: str = "", @@ -42,9 +87,10 @@ if sys.version_info >= (3, 10): openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, encoding: str | None = None, errors: str | None = None, - ) -> FileInput[str]: ... + ) -> None: ... @overload - def input( + def __init__( + self: FileInput[bytes], files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, inplace: bool = False, backup: str = "", @@ -53,9 +99,10 @@ if sys.version_info >= (3, 10): openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, encoding: None = None, errors: None = None, - ) -> FileInput[bytes]: ... + ) -> None: ... @overload - def input( + def __init__( + self: FileInput[Any], files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, inplace: bool = False, backup: str = "", @@ -64,119 +111,7 @@ if sys.version_info >= (3, 10): openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, encoding: str | None = None, errors: str | None = None, - ) -> FileInput[Any]: ... - -else: - # bufsize is dropped and mode and openhook become keyword-only - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: _TextMode = "r", - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, - ) -> FileInput[str]: ... - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> FileInput[bytes]: ... - @overload - def input( - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> FileInput[Any]: ... - -def close() -> None: ... -def nextfile() -> None: ... -def filename() -> str: ... -def lineno() -> int: ... -def filelineno() -> int: ... -def fileno() -> int: ... -def isfirstline() -> bool: ... -def isstdin() -> bool: ... - -class FileInput(Iterator[AnyStr]): - if sys.version_info >= (3, 10): - # encoding and errors are added - @overload - def __init__( - self: FileInput[str], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: _TextMode = "r", - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, - encoding: str | None = None, - errors: str | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[bytes], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - encoding: None = None, - errors: None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[Any], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - encoding: str | None = None, - errors: str | None = None, - ) -> None: ... - - else: - # bufsize is dropped and mode and openhook become keyword-only - @overload - def __init__( - self: FileInput[str], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: _TextMode = "r", - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[bytes], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: Literal["rb"], - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = None, - ) -> None: ... - @overload - def __init__( - self: FileInput[Any], - files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = None, - inplace: bool = False, - backup: str = "", - *, - mode: str, - openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = None, - ) -> None: ... + ) -> None: ... def __del__(self) -> None: ... def close(self) -> None: ... @@ -199,17 +134,8 @@ class FileInput(Iterator[AnyStr]): def isstdin(self) -> bool: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -if sys.version_info >= (3, 10): - def hook_compressed( - filename: StrOrBytesPath, mode: str, *, encoding: str | None = None, errors: str | None = None - ) -> IO[Any]: ... - -else: - def hook_compressed(filename: StrOrBytesPath, mode: str) -> IO[Any]: ... - -if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10. Use `fileinput.input` or `fileinput.FileInput` instead.") - def hook_encoded(encoding: str, errors: str | None = None) -> Callable[[StrOrBytesPath, str], IO[Any]]: ... - -else: - def hook_encoded(encoding: str, errors: str | None = None) -> Callable[[StrOrBytesPath, str], IO[Any]]: ... +def hook_compressed( + filename: StrOrBytesPath, mode: str, *, encoding: str | None = None, errors: str | None = None +) -> IO[Any]: ... +@deprecated("Deprecated since Python 3.10. Use `fileinput.input` or `fileinput.FileInput` instead.") +def hook_encoded(encoding: str, errors: str | None = None) -> Callable[[StrOrBytesPath, str], IO[Any]]: ... diff --git a/mypy/typeshed/stdlib/fnmatch.pyi b/mypy/typeshed/stdlib/fnmatch.pyi index 345c4576497de..018139dc482bf 100644 --- a/mypy/typeshed/stdlib/fnmatch.pyi +++ b/mypy/typeshed/stdlib/fnmatch.pyi @@ -1,15 +1,16 @@ import sys from collections.abc import Iterable +from os import PathLike from typing import AnyStr __all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] if sys.version_info >= (3, 14): __all__ += ["filterfalse"] -def fnmatch(name: AnyStr, pat: AnyStr) -> bool: ... +def fnmatch(name: AnyStr | PathLike[AnyStr], pat: AnyStr | PathLike[AnyStr]) -> bool: ... def fnmatchcase(name: AnyStr, pat: AnyStr) -> bool: ... -def filter(names: Iterable[AnyStr], pat: AnyStr) -> list[AnyStr]: ... +def filter(names: Iterable[AnyStr | PathLike[AnyStr]], pat: AnyStr | PathLike[AnyStr]) -> list[AnyStr]: ... def translate(pat: str) -> str: ... if sys.version_info >= (3, 14): - def filterfalse(names: Iterable[AnyStr], pat: AnyStr) -> list[AnyStr]: ... + def filterfalse(names: Iterable[AnyStr | PathLike[AnyStr]], pat: AnyStr | PathLike[AnyStr]) -> list[AnyStr]: ... diff --git a/mypy/typeshed/stdlib/formatter.pyi b/mypy/typeshed/stdlib/formatter.pyi deleted file mode 100644 index 05c3c8b3dd41c..0000000000000 --- a/mypy/typeshed/stdlib/formatter.pyi +++ /dev/null @@ -1,88 +0,0 @@ -from collections.abc import Iterable -from typing import IO, Any -from typing_extensions import TypeAlias - -AS_IS: None -_FontType: TypeAlias = tuple[str, bool, bool, bool] -_StylesType: TypeAlias = tuple[Any, ...] - -class NullFormatter: - writer: NullWriter | None - def __init__(self, writer: NullWriter | None = None) -> None: ... - def end_paragraph(self, blankline: int) -> None: ... - def add_line_break(self) -> None: ... - def add_hor_rule(self, *args: Any, **kw: Any) -> None: ... - def add_label_data(self, format: str, counter: int, blankline: int | None = None) -> None: ... - def add_flowing_data(self, data: str) -> None: ... - def add_literal_data(self, data: str) -> None: ... - def flush_softspace(self) -> None: ... - def push_alignment(self, align: str | None) -> None: ... - def pop_alignment(self) -> None: ... - def push_font(self, x: _FontType) -> None: ... - def pop_font(self) -> None: ... - def push_margin(self, margin: int) -> None: ... - def pop_margin(self) -> None: ... - def set_spacing(self, spacing: str | None) -> None: ... - def push_style(self, *styles: _StylesType) -> None: ... - def pop_style(self, n: int = 1) -> None: ... - def assert_line_data(self, flag: int = 1) -> None: ... - -class AbstractFormatter: - writer: NullWriter - align: str | None - align_stack: list[str | None] - font_stack: list[_FontType] - margin_stack: list[int] - spacing: str | None - style_stack: Any - nospace: int - softspace: int - para_end: int - parskip: int - hard_break: int - have_label: int - def __init__(self, writer: NullWriter) -> None: ... - def end_paragraph(self, blankline: int) -> None: ... - def add_line_break(self) -> None: ... - def add_hor_rule(self, *args: Any, **kw: Any) -> None: ... - def add_label_data(self, format: str, counter: int, blankline: int | None = None) -> None: ... - def format_counter(self, format: Iterable[str], counter: int) -> str: ... - def format_letter(self, case: str, counter: int) -> str: ... - def format_roman(self, case: str, counter: int) -> str: ... - def add_flowing_data(self, data: str) -> None: ... - def add_literal_data(self, data: str) -> None: ... - def flush_softspace(self) -> None: ... - def push_alignment(self, align: str | None) -> None: ... - def pop_alignment(self) -> None: ... - def push_font(self, font: _FontType) -> None: ... - def pop_font(self) -> None: ... - def push_margin(self, margin: int) -> None: ... - def pop_margin(self) -> None: ... - def set_spacing(self, spacing: str | None) -> None: ... - def push_style(self, *styles: _StylesType) -> None: ... - def pop_style(self, n: int = 1) -> None: ... - def assert_line_data(self, flag: int = 1) -> None: ... - -class NullWriter: - def flush(self) -> None: ... - def new_alignment(self, align: str | None) -> None: ... - def new_font(self, font: _FontType) -> None: ... - def new_margin(self, margin: int, level: int) -> None: ... - def new_spacing(self, spacing: str | None) -> None: ... - def new_styles(self, styles: tuple[Any, ...]) -> None: ... - def send_paragraph(self, blankline: int) -> None: ... - def send_line_break(self) -> None: ... - def send_hor_rule(self, *args: Any, **kw: Any) -> None: ... - def send_label_data(self, data: str) -> None: ... - def send_flowing_data(self, data: str) -> None: ... - def send_literal_data(self, data: str) -> None: ... - -class AbstractWriter(NullWriter): ... - -class DumbWriter(NullWriter): - file: IO[str] - maxcol: int - def __init__(self, file: IO[str] | None = None, maxcol: int = 72) -> None: ... - def reset(self) -> None: ... - -def test(file: str | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/fractions.pyi b/mypy/typeshed/stdlib/fractions.pyi index ef4066aa65b52..42947f2e2266a 100644 --- a/mypy/typeshed/stdlib/fractions.pyi +++ b/mypy/typeshed/stdlib/fractions.pyi @@ -2,8 +2,8 @@ import sys from collections.abc import Callable from decimal import Decimal from numbers import Rational, Real -from typing import Any, Literal, Protocol, SupportsIndex, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, Literal, Protocol, SupportsIndex, TypeAlias, overload, type_check_only +from typing_extensions import Self _ComparableNum: TypeAlias = int | float | Decimal | Real @@ -15,11 +15,11 @@ class _ConvertibleToIntegerRatio(Protocol): class Fraction(Rational): __slots__ = ("_numerator", "_denominator") + @overload def __new__(cls, numerator: int | Rational = 0, denominator: int | Rational | None = None) -> Self: ... @overload def __new__(cls, numerator: float | Decimal | str) -> Self: ... - if sys.version_info >= (3, 14): @overload def __new__(cls, numerator: _ConvertibleToIntegerRatio) -> Self: ... @@ -37,78 +37,93 @@ class Fraction(Rational): def numerator(a) -> int: ... @property def denominator(a) -> int: ... + @overload def __add__(a, b: int | Fraction) -> Fraction: ... @overload def __add__(a, b: float) -> float: ... @overload def __add__(a, b: complex) -> complex: ... + @overload def __radd__(b, a: int | Fraction) -> Fraction: ... @overload def __radd__(b, a: float) -> float: ... @overload def __radd__(b, a: complex) -> complex: ... + @overload def __sub__(a, b: int | Fraction) -> Fraction: ... @overload def __sub__(a, b: float) -> float: ... @overload def __sub__(a, b: complex) -> complex: ... + @overload def __rsub__(b, a: int | Fraction) -> Fraction: ... @overload def __rsub__(b, a: float) -> float: ... @overload def __rsub__(b, a: complex) -> complex: ... + @overload def __mul__(a, b: int | Fraction) -> Fraction: ... @overload def __mul__(a, b: float) -> float: ... @overload def __mul__(a, b: complex) -> complex: ... + @overload def __rmul__(b, a: int | Fraction) -> Fraction: ... @overload def __rmul__(b, a: float) -> float: ... @overload def __rmul__(b, a: complex) -> complex: ... + @overload def __truediv__(a, b: int | Fraction) -> Fraction: ... @overload def __truediv__(a, b: float) -> float: ... @overload def __truediv__(a, b: complex) -> complex: ... + @overload def __rtruediv__(b, a: int | Fraction) -> Fraction: ... @overload def __rtruediv__(b, a: float) -> float: ... @overload def __rtruediv__(b, a: complex) -> complex: ... + @overload def __floordiv__(a, b: int | Fraction) -> int: ... @overload def __floordiv__(a, b: float) -> float: ... + @overload def __rfloordiv__(b, a: int | Fraction) -> int: ... @overload def __rfloordiv__(b, a: float) -> float: ... + @overload def __mod__(a, b: int | Fraction) -> Fraction: ... @overload def __mod__(a, b: float) -> float: ... + @overload def __rmod__(b, a: int | Fraction) -> Fraction: ... @overload def __rmod__(b, a: float) -> float: ... + @overload def __divmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... @overload def __divmod__(a, b: float) -> tuple[float, Fraction]: ... + @overload def __rdivmod__(a, b: int | Fraction) -> tuple[int, Fraction]: ... @overload def __rdivmod__(a, b: float) -> tuple[float, Fraction]: ... + if sys.version_info >= (3, 14): @overload def __pow__(a, b: int, modulo: None = None) -> Fraction: ... @@ -123,6 +138,7 @@ class Fraction(Rational): def __pow__(a, b: float | Fraction) -> float: ... @overload def __pow__(a, b: complex) -> complex: ... + if sys.version_info >= (3, 14): @overload def __rpow__(b, a: float | Fraction, modulo: None = None) -> float: ... @@ -140,10 +156,12 @@ class Fraction(Rational): def __trunc__(a) -> int: ... def __floor__(a) -> int: ... def __ceil__(a) -> int: ... + @overload def __round__(self, ndigits: None = None) -> int: ... @overload def __round__(self, ndigits: int) -> Fraction: ... + def __hash__(self) -> int: ... # type: ignore[override] def __eq__(a, b: object) -> bool: ... def __lt__(a, b: _ComparableNum) -> bool: ... diff --git a/mypy/typeshed/stdlib/ftplib.pyi b/mypy/typeshed/stdlib/ftplib.pyi index 73eaa8a34e578..1b7222fc94f75 100644 --- a/mypy/typeshed/stdlib/ftplib.pyi +++ b/mypy/typeshed/stdlib/ftplib.pyi @@ -154,6 +154,7 @@ class FTP_TLS(FTP): *, encoding: str = "utf-8", ) -> None: ... + ssl_version: int keyfile: StrOrBytesPath | None certfile: StrOrBytesPath | None diff --git a/mypy/typeshed/stdlib/functools.pyi b/mypy/typeshed/stdlib/functools.pyi index 57bc3f179f7ac..5619a64f6ed20 100644 --- a/mypy/typeshed/stdlib/functools.pyi +++ b/mypy/typeshed/stdlib/functools.pyi @@ -3,8 +3,21 @@ import types from _typeshed import SupportsAllComparisons, SupportsItems from collections.abc import Callable, Hashable, Iterable, Sized from types import GenericAlias -from typing import Any, Final, Generic, Literal, NamedTuple, TypedDict, TypeVar, final, overload, type_check_only -from typing_extensions import ParamSpec, Self, TypeAlias, disjoint_base +from typing import ( + Any, + Final, + Generic, + Literal, + NamedTuple, + ParamSpec, + TypeAlias, + TypedDict, + TypeVar, + final, + overload, + type_check_only, +) +from typing_extensions import Self, disjoint_base __all__ = [ "update_wrapper", @@ -34,7 +47,6 @@ _RWrapper = TypeVar("_RWrapper") if sys.version_info >= (3, 14): @overload def reduce(function: Callable[[_T, _S], _T], iterable: Iterable[_S], /, initial: _T) -> _T: ... - else: @overload def reduce(function: Callable[[_T, _S], _T], iterable: Iterable[_S], initial: _T, /) -> _T: ... @@ -154,6 +166,7 @@ else: def total_ordering(cls: type[_T]) -> type[_T]: ... def cmp_to_key(mycmp: Callable[[_T, _T], int]) -> Callable[[_T], SupportsAllComparisons]: ... + @disjoint_base class partial(Generic[_T]): @property @@ -198,6 +211,7 @@ else: class _SingleDispatchCallable(Generic[_T]): registry: types.MappingProxyType[Any, Callable[..., _T]] def dispatch(self, cls: Any) -> Callable[..., _T]: ... + # @fun.register(complex) # def _(arg, verbose=False): ... @overload @@ -209,6 +223,7 @@ class _SingleDispatchCallable(Generic[_T]): # fun.register(int, lambda x: x) @overload def register(self, cls: _RegType, func: Callable[..., _T]) -> Callable[..., _T]: ... + def _clear_cache(self) -> None: ... def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ... @@ -220,22 +235,26 @@ class singledispatchmethod(Generic[_T]): def __init__(self, func: Callable[..., _T]) -> None: ... @property def __isabstractmethod__(self) -> bool: ... + @overload def register(self, cls: _RegType, method: None = None) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... @overload def register(self, cls: Callable[..., _T], method: None = None) -> Callable[..., _T]: ... @overload def register(self, cls: _RegType, method: Callable[..., _T]) -> Callable[..., _T]: ... + def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]: ... class cached_property(Generic[_T_co]): func: Callable[[Any], _T_co] attrname: str | None def __init__(self, func: Callable[[Any], _T_co]) -> None: ... + @overload def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... @overload def __get__(self, instance: object, owner: type[Any] | None = None) -> _T_co: ... + def __set_name__(self, owner: type[Any], name: str) -> None: ... # __set__ is not defined at runtime, but @cached_property is designed to be settable def __set__(self, instance: object, value: _T_co) -> None: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] diff --git a/mypy/typeshed/stdlib/gc.pyi b/mypy/typeshed/stdlib/gc.pyi index ec1ed2681c5c8..dc20a570cc89b 100644 --- a/mypy/typeshed/stdlib/gc.pyi +++ b/mypy/typeshed/stdlib/gc.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import Any, Final, Literal -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, TypeAlias DEBUG_COLLECTABLE: Final = 2 DEBUG_LEAK: Final = 38 diff --git a/mypy/typeshed/stdlib/genericpath.pyi b/mypy/typeshed/stdlib/genericpath.pyi index 3caed77a661ac..07c58cc496b60 100644 --- a/mypy/typeshed/stdlib/genericpath.pyi +++ b/mypy/typeshed/stdlib/genericpath.pyi @@ -3,7 +3,7 @@ import sys from _typeshed import BytesPath, FileDescriptorOrPath, StrOrBytesPath, StrPath, SupportsRichComparisonT from collections.abc import Sequence from typing import Literal, NewType, overload -from typing_extensions import LiteralString +from typing_extensions import LiteralString, deprecated __all__ = [ "commonprefix", @@ -23,22 +23,41 @@ if sys.version_info >= (3, 12): __all__ += ["islink"] if sys.version_info >= (3, 13): __all__ += ["isjunction", "isdevdrive", "lexists"] +if sys.version_info >= (3, 15): + __all__ += ["ALL_BUT_LAST"] # All overloads can return empty string. Ideally, Literal[""] would be a valid # Iterable[T], so that list[T] | Literal[""] could be used as a return # type. But because this only works when T is str, we need Sequence[T] instead. -@overload -def commonprefix(m: Sequence[LiteralString]) -> LiteralString: ... -@overload -def commonprefix(m: Sequence[StrPath]) -> str: ... -@overload -def commonprefix(m: Sequence[BytesPath]) -> bytes | Literal[""]: ... -@overload -def commonprefix(m: Sequence[list[SupportsRichComparisonT]]) -> Sequence[SupportsRichComparisonT]: ... -@overload -def commonprefix(m: Sequence[tuple[SupportsRichComparisonT, ...]]) -> Sequence[SupportsRichComparisonT]: ... +if sys.version_info >= (3, 15): + @overload + @deprecated("Deprecated since Python 3.15; use os.path.commonpath() for path prefixes.") + def commonprefix(m: Sequence[LiteralString], /) -> LiteralString: ... + @overload + @deprecated("Deprecated since Python 3.15; use os.path.commonpath() for path prefixes.") + def commonprefix(m: Sequence[StrPath], /) -> str: ... + @overload + @deprecated("Deprecated since Python 3.15; use os.path.commonpath() for path prefixes.") + def commonprefix(m: Sequence[BytesPath], /) -> bytes | Literal[""]: ... + @overload + @deprecated("Deprecated since Python 3.15; use os.path.commonpath() for path prefixes.") + def commonprefix(m: Sequence[list[SupportsRichComparisonT]], /) -> Sequence[SupportsRichComparisonT]: ... + @overload + @deprecated("Deprecated since Python 3.15; use os.path.commonpath() for path prefixes.") + def commonprefix(m: Sequence[tuple[SupportsRichComparisonT, ...]], /) -> Sequence[SupportsRichComparisonT]: ... +else: + @overload + def commonprefix(m: Sequence[LiteralString]) -> LiteralString: ... + @overload + def commonprefix(m: Sequence[StrPath]) -> str: ... + @overload + def commonprefix(m: Sequence[BytesPath]) -> bytes | Literal[""]: ... + @overload + def commonprefix(m: Sequence[list[SupportsRichComparisonT]]) -> Sequence[SupportsRichComparisonT]: ... + @overload + def commonprefix(m: Sequence[tuple[SupportsRichComparisonT, ...]]) -> Sequence[SupportsRichComparisonT]: ... + def exists(path: FileDescriptorOrPath) -> bool: ... -def getsize(filename: FileDescriptorOrPath) -> int: ... def isfile(path: FileDescriptorOrPath) -> bool: ... def isdir(s: FileDescriptorOrPath) -> bool: ... @@ -47,12 +66,23 @@ if sys.version_info >= (3, 12): # These return float if os.stat_float_times() == True, # but int is a subclass of float. -def getatime(filename: FileDescriptorOrPath) -> float: ... -def getmtime(filename: FileDescriptorOrPath) -> float: ... -def getctime(filename: FileDescriptorOrPath) -> float: ... -def samefile(f1: FileDescriptorOrPath, f2: FileDescriptorOrPath) -> bool: ... def sameopenfile(fp1: int, fp2: int) -> bool: ... -def samestat(s1: os.stat_result, s2: os.stat_result) -> bool: ... + +if sys.version_info >= (3, 15): + def getsize(filename: FileDescriptorOrPath, /) -> int: ... + def getatime(filename: FileDescriptorOrPath, /) -> float: ... + def getmtime(filename: FileDescriptorOrPath, /) -> float: ... + def getctime(filename: FileDescriptorOrPath, /) -> float: ... + def samefile(f1: FileDescriptorOrPath, f2: FileDescriptorOrPath, /) -> bool: ... + def samestat(s1: os.stat_result, s2: os.stat_result, /) -> bool: ... + +else: + def getsize(filename: FileDescriptorOrPath) -> int: ... + def getatime(filename: FileDescriptorOrPath) -> float: ... + def getmtime(filename: FileDescriptorOrPath) -> float: ... + def getctime(filename: FileDescriptorOrPath) -> float: ... + def samefile(f1: FileDescriptorOrPath, f2: FileDescriptorOrPath) -> bool: ... + def samestat(s1: os.stat_result, s2: os.stat_result) -> bool: ... if sys.version_info >= (3, 13): def isjunction(path: StrOrBytesPath) -> bool: ... @@ -62,3 +92,7 @@ if sys.version_info >= (3, 13): # Added in Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 _AllowMissingType = NewType("_AllowMissingType", object) ALLOW_MISSING: _AllowMissingType + +if sys.version_info >= (3, 15): + _AllButLastType = NewType("_AllButLastType", object) + ALL_BUT_LAST: _AllButLastType diff --git a/mypy/typeshed/stdlib/gettext.pyi b/mypy/typeshed/stdlib/gettext.pyi index e9ffd7a4a4a42..aac2d3edf7d98 100644 --- a/mypy/typeshed/stdlib/gettext.pyi +++ b/mypy/typeshed/stdlib/gettext.pyi @@ -110,8 +110,8 @@ if sys.version_info >= (3, 11): class_: Callable[[io.BufferedReader], NullTranslations] | None = None, fallback: bool = False, ) -> NullTranslations: ... - def install(domain: str, localedir: StrPath | None = None, *, names: Container[str] | None = None) -> None: ... + def install(domain: str, localedir: StrPath | None = None, *, names: Container[str] | None = None) -> None: ... else: @overload def translation( @@ -150,6 +150,7 @@ else: fallback: bool = False, codeset: str | None = ..., ) -> NullTranslations: ... + @overload def install(domain: str, localedir: StrPath | None = None, names: Container[str] | None = None) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/glob.pyi b/mypy/typeshed/stdlib/glob.pyi index 942fd73961963..bdfb2cfbcfedd 100644 --- a/mypy/typeshed/stdlib/glob.pyi +++ b/mypy/typeshed/stdlib/glob.pyi @@ -9,7 +9,7 @@ __all__ = ["escape", "glob", "iglob"] if sys.version_info >= (3, 13): __all__ += ["translate"] -if sys.version_info >= (3, 10): +if sys.version_info < (3, 15): @deprecated( "Deprecated since Python 3.10; will be removed in Python 3.15. Use `glob.glob()` with the *root_dir* argument instead." ) @@ -19,10 +19,6 @@ if sys.version_info >= (3, 10): ) def glob1(dirname: AnyStr, pattern: AnyStr) -> list[AnyStr]: ... -else: - def glob0(dirname: AnyStr, pattern: AnyStr) -> list[AnyStr]: ... - def glob1(dirname: AnyStr, pattern: AnyStr) -> list[AnyStr]: ... - if sys.version_info >= (3, 11): def glob( pathname: AnyStr, @@ -41,7 +37,7 @@ if sys.version_info >= (3, 11): include_hidden: bool = False, ) -> Iterator[AnyStr]: ... -elif sys.version_info >= (3, 10): +else: def glob( pathname: AnyStr, *, root_dir: StrOrBytesPath | None = None, dir_fd: int | None = None, recursive: bool = False ) -> list[AnyStr]: ... @@ -49,10 +45,6 @@ elif sys.version_info >= (3, 10): pathname: AnyStr, *, root_dir: StrOrBytesPath | None = None, dir_fd: int | None = None, recursive: bool = False ) -> Iterator[AnyStr]: ... -else: - def glob(pathname: AnyStr, *, recursive: bool = False) -> list[AnyStr]: ... - def iglob(pathname: AnyStr, *, recursive: bool = False) -> Iterator[AnyStr]: ... - def escape(pathname: AnyStr) -> AnyStr: ... def has_magic(s: str | bytes) -> bool: ... # undocumented diff --git a/mypy/typeshed/stdlib/graphlib.pyi b/mypy/typeshed/stdlib/graphlib.pyi index 1ca8cbe12b085..f0ac72b6135e8 100644 --- a/mypy/typeshed/stdlib/graphlib.pyi +++ b/mypy/typeshed/stdlib/graphlib.pyi @@ -15,6 +15,7 @@ class TopologicalSorter(Generic[_T]): def __init__(self, graph: None = None) -> None: ... @overload def __init__(self, graph: SupportsItems[_T, Iterable[_T]]) -> None: ... + def add(self, node: _T, *predecessors: _T) -> None: ... def prepare(self) -> None: ... def is_active(self) -> bool: ... diff --git a/mypy/typeshed/stdlib/grp.pyi b/mypy/typeshed/stdlib/grp.pyi index 965ecece2a56d..9f372b4d63dc8 100644 --- a/mypy/typeshed/stdlib/grp.pyi +++ b/mypy/typeshed/stdlib/grp.pyi @@ -5,8 +5,7 @@ from typing import Any, Final, final if sys.platform != "win32": @final class struct_group(structseq[Any], tuple[str, str | None, int, list[str]]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("gr_name", "gr_passwd", "gr_gid", "gr_mem") + __match_args__: Final = ("gr_name", "gr_passwd", "gr_gid", "gr_mem") @property def gr_name(self) -> str: ... diff --git a/mypy/typeshed/stdlib/gzip.pyi b/mypy/typeshed/stdlib/gzip.pyi index b18f76f06e3ee..4322e6c84c905 100644 --- a/mypy/typeshed/stdlib/gzip.pyi +++ b/mypy/typeshed/stdlib/gzip.pyi @@ -2,8 +2,8 @@ import sys import zlib from _typeshed import ReadableBuffer, SizedBuffer, StrOrBytesPath, WriteableBuffer from io import FileIO, TextIOWrapper -from typing import Final, Literal, Protocol, overload, type_check_only -from typing_extensions import TypeAlias, deprecated +from typing import Final, Literal, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import deprecated if sys.version_info >= (3, 14): from compression._common._streams import BaseStream, DecompressReader @@ -96,6 +96,7 @@ class GzipFile(BaseStream): name: str compress: zlib._Compress fileobj: _ReadableFileobj | _WritableFileobj + @overload def __init__( self, @@ -141,6 +142,7 @@ class GzipFile(BaseStream): fileobj: _ReadableFileobj | _WritableFileobj | None = None, mtime: float | None = None, ) -> None: ... + if sys.version_info < (3, 12): @property @deprecated("Deprecated since Python 2.6; removed in Python 3.12. Use `name` attribute instead.") @@ -167,7 +169,10 @@ class GzipFile(BaseStream): class _GzipReader(DecompressReader): def __init__(self, fp: _ReadableFileobj) -> None: ... -if sys.version_info >= (3, 14): +if sys.version_info >= (3, 15): + def compress(data: SizedBuffer, compresslevel: int = 6, *, mtime: float = 0) -> bytes: ... + +elif sys.version_info >= (3, 14): def compress(data: SizedBuffer, compresslevel: int = 9, *, mtime: float = 0) -> bytes: ... else: diff --git a/mypy/typeshed/stdlib/hashlib.pyi b/mypy/typeshed/stdlib/hashlib.pyi index 1763c23182736..50bc8e21f1d52 100644 --- a/mypy/typeshed/stdlib/hashlib.pyi +++ b/mypy/typeshed/stdlib/hashlib.pyi @@ -22,7 +22,7 @@ from _typeshed import ReadableBuffer from collections.abc import Callable, Set as AbstractSet from typing import Protocol, type_check_only -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 15): __all__ = ( "md5", "sha1", @@ -41,8 +41,31 @@ if sys.version_info >= (3, 11): "new", "algorithms_guaranteed", "algorithms_available", + "file_digest", "pbkdf2_hmac", + "scrypt", + ) +elif sys.version_info >= (3, 11): + __all__ = ( + "md5", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "blake2b", + "blake2s", + "sha3_224", + "sha3_256", + "sha3_384", + "sha3_512", + "shake_128", + "shake_256", + "new", + "algorithms_guaranteed", + "algorithms_available", "file_digest", + "pbkdf2_hmac", ) else: __all__ = ( diff --git a/mypy/typeshed/stdlib/heapq.pyi b/mypy/typeshed/stdlib/heapq.pyi index ff8ba7ff1f826..7969aa4e00152 100644 --- a/mypy/typeshed/stdlib/heapq.pyi +++ b/mypy/typeshed/stdlib/heapq.pyi @@ -1,8 +1,8 @@ import sys from _heapq import * -from _typeshed import SupportsRichComparison +from _typeshed import SupportsRichComparison, SupportsRichComparisonT as _T from collections.abc import Callable, Generator, Iterable -from typing import Any, Final, TypeVar +from typing import Final, TypeVar, overload __all__ = ["heappush", "heappop", "heapify", "heapreplace", "merge", "nlargest", "nsmallest", "heappushpop"] @@ -14,9 +14,19 @@ _S = TypeVar("_S") __about__: Final[str] -def merge( - *iterables: Iterable[_S], key: Callable[[_S], SupportsRichComparison] | None = None, reverse: bool = False -) -> Generator[_S]: ... -def nlargest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison] | None = None) -> list[_S]: ... -def nsmallest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison] | None = None) -> list[_S]: ... -def _heapify_max(heap: list[Any], /) -> None: ... # undocumented +@overload +def merge(*iterables: Iterable[_S], key: Callable[[_S], SupportsRichComparison], reverse: bool = False) -> Generator[_S]: ... +@overload +def merge(*iterables: Iterable[_T], key: None = None, reverse: bool = False) -> Generator[_T]: ... + +@overload +def nlargest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison]) -> list[_S]: ... +@overload +def nlargest(n: int, iterable: Iterable[_T], key: None = None) -> list[_T]: ... + +@overload +def nsmallest(n: int, iterable: Iterable[_S], key: Callable[[_S], SupportsRichComparison]) -> list[_S]: ... +@overload +def nsmallest(n: int, iterable: Iterable[_T], key: None = None) -> list[_T]: ... + +def _heapify_max(heap: list[SupportsRichComparison], /) -> None: ... # undocumented diff --git a/mypy/typeshed/stdlib/hmac.pyi b/mypy/typeshed/stdlib/hmac.pyi index 070c59b1c166d..9beabcc4dd92e 100644 --- a/mypy/typeshed/stdlib/hmac.pyi +++ b/mypy/typeshed/stdlib/hmac.pyi @@ -2,8 +2,7 @@ from _hashlib import _HashObject, compare_digest as compare_digest from _typeshed import ReadableBuffer, SizedBuffer from collections.abc import Callable from types import ModuleType -from typing import overload -from typing_extensions import TypeAlias +from typing import TypeAlias, overload _DigestMod: TypeAlias = str | Callable[[], _HashObject] | ModuleType diff --git a/mypy/typeshed/stdlib/html/__init__.pyi b/mypy/typeshed/stdlib/html/__init__.pyi index afba90832535d..71e971d150449 100644 --- a/mypy/typeshed/stdlib/html/__init__.pyi +++ b/mypy/typeshed/stdlib/html/__init__.pyi @@ -1,6 +1,7 @@ -from typing import AnyStr +import re __all__ = ["escape", "unescape"] -def escape(s: AnyStr, quote: bool = True) -> AnyStr: ... -def unescape(s: AnyStr) -> AnyStr: ... +def escape(s: str, quote: bool = True) -> str: ... +def unescape(s: str) -> str: ... +def _replace_charref(s: re.Match[str]) -> str: ... diff --git a/mypy/typeshed/stdlib/http/client.pyi b/mypy/typeshed/stdlib/http/client.pyi index 699ef0e4c6d65..d22335b56c547 100644 --- a/mypy/typeshed/stdlib/http/client.pyi +++ b/mypy/typeshed/stdlib/http/client.pyi @@ -7,8 +7,8 @@ from _typeshed import MaybeNone, ReadableBuffer, StrOrBytesPath, SupportsRead, S from collections.abc import Callable, Iterable, Iterator, Mapping from email._policybase import _MessageT from socket import socket -from typing import BinaryIO, Final, TypeVar, overload -from typing_extensions import Self, TypeAlias, deprecated +from typing import BinaryIO, Final, TypeAlias, TypeVar, overload +from typing_extensions import Self, deprecated __all__ = [ "HTTPResponse", @@ -149,10 +149,12 @@ class HTTPResponse(io.BufferedIOBase, BinaryIO): # type: ignore[misc] # incomp def read1(self, n: int = -1) -> bytes: ... def readinto(self, b: WriteableBuffer) -> int: ... def readline(self, limit: int = -1) -> bytes: ... # type: ignore[override] + @overload def getheader(self, name: str) -> str | None: ... @overload def getheader(self, name: str, default: _T) -> str | _T: ... + def getheaders(self) -> list[tuple[str, str]]: ... def isclosed(self) -> bool: ... def __iter__(self) -> Iterator[bytes]: ... @@ -178,14 +180,27 @@ class HTTPConnection: host: str port: int sock: socket | MaybeNone # can be `None` if `.connect()` was not called - def __init__( - self, - host: str, - port: int | None = None, - timeout: float | None = ..., - source_address: tuple[str, int] | None = None, - blocksize: int = 8192, - ) -> None: ... + if sys.version_info >= (3, 15): + def __init__( + self, + host: str, + port: int | None = None, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + *, + max_response_headers: int | None = None, + ) -> None: ... + else: + def __init__( + self, + host: str, + port: int | None = None, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + ) -> None: ... + def request( self, method: str, @@ -211,7 +226,19 @@ class HTTPConnection: class HTTPSConnection(HTTPConnection): # Can be `None` if `.connect()` was not called: sock: ssl.SSLSocket | MaybeNone - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 15): + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: float | None = ..., + source_address: tuple[str, int] | None = None, + context: ssl.SSLContext | None = None, + blocksize: int = 8192, + max_response_headers: int | None = None, + ) -> None: ... + elif sys.version_info >= (3, 12): def __init__( self, host: str, @@ -255,6 +282,7 @@ class HTTPSConnection(HTTPConnection): check_hostname: bool | None = None, blocksize: int = 8192, ) -> None: ... + key_file: StrOrBytesPath | None cert_file: StrOrBytesPath | None diff --git a/mypy/typeshed/stdlib/http/cookiejar.pyi b/mypy/typeshed/stdlib/http/cookiejar.pyi index 31e1d3fc83785..2cbe432c55866 100644 --- a/mypy/typeshed/stdlib/http/cookiejar.pyi +++ b/mypy/typeshed/stdlib/http/cookiejar.pyi @@ -1,4 +1,3 @@ -import sys from _typeshed import StrPath from collections.abc import Iterator, Sequence from http.client import HTTPResponse @@ -49,9 +48,7 @@ class FileCookieJar(CookieJar): def load(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... def revert(self, filename: str | None = None, ignore_discard: bool = False, ignore_expires: bool = False) -> None: ... -class MozillaCookieJar(FileCookieJar): - if sys.version_info < (3, 10): - header: ClassVar[str] # undocumented +class MozillaCookieJar(FileCookieJar): ... class LWPCookieJar(FileCookieJar): def as_lwp_str(self, ignore_discard: bool = True, ignore_expires: bool = True) -> str: ... # undocumented @@ -151,9 +148,11 @@ class Cookie: rfc2109: bool = False, ) -> None: ... def has_nonstandard_attr(self, name: str) -> bool: ... + @overload def get_nonstandard_attr(self, name: str) -> str | None: ... @overload def get_nonstandard_attr(self, name: str, default: _T) -> str | _T: ... + def set_nonstandard_attr(self, name: str, value: str) -> None: ... def is_expired(self, now: int | None = None) -> bool: ... diff --git a/mypy/typeshed/stdlib/http/cookies.pyi b/mypy/typeshed/stdlib/http/cookies.pyi index eadf054c3a5f4..bdec1068b5ea0 100644 --- a/mypy/typeshed/stdlib/http/cookies.pyi +++ b/mypy/typeshed/stdlib/http/cookies.pyi @@ -1,18 +1,17 @@ -from _typeshed import MaybeNone -from collections.abc import Iterable, Mapping +from _typeshed import MaybeNone, SupportsItems, SupportsKeysAndGetItem +from collections.abc import Container, Iterable from types import GenericAlias from typing import Any, Generic, TypeVar, overload -from typing_extensions import TypeAlias __all__ = ["CookieError", "BaseCookie", "SimpleCookie"] -_DataType: TypeAlias = str | Mapping[str, str | Morsel[Any]] _T = TypeVar("_T") @overload def _quote(str: None) -> None: ... @overload def _quote(str: str) -> str: ... + @overload def _unquote(str: None) -> None: ... @overload @@ -31,27 +30,24 @@ class Morsel(dict[str, Any], Generic[_T]): def set(self, key: str, val: str, coded_val: _T) -> None: ... def setdefault(self, key: str, val: str | None = None) -> str: ... # The dict update can also get a keywords argument so this is incompatible - @overload # type: ignore[override] - def update(self, values: Mapping[str, str]) -> None: ... - @overload - def update(self, values: Iterable[tuple[str, str]]) -> None: ... + def update(self, values: Iterable[tuple[str, str]] | SupportsKeysAndGetItem[str, str]) -> None: ... # type: ignore[override] def isReservedKey(self, K: str) -> bool: ... - def output(self, attrs: list[str] | None = None, header: str = "Set-Cookie:") -> str: ... + def output(self, attrs: Container[str] | None = None, header: str = "Set-Cookie:") -> str: ... __str__ = output - def js_output(self, attrs: list[str] | None = None) -> str: ... - def OutputString(self, attrs: list[str] | None = None) -> str: ... + def js_output(self, attrs: Container[str] | None = None) -> str: ... + def OutputString(self, attrs: Container[str] | None = None) -> str: ... def __eq__(self, morsel: object) -> bool: ... def __setitem__(self, K: str, V: Any) -> None: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class BaseCookie(dict[str, Morsel[_T]], Generic[_T]): - def __init__(self, input: _DataType | None = None) -> None: ... + def __init__(self, input: str | SupportsItems[str, str | Morsel[Any]] | None = None) -> None: ... def value_decode(self, val: str) -> tuple[_T, str]: ... def value_encode(self, val: _T) -> tuple[str, str]: ... - def output(self, attrs: list[str] | None = None, header: str = "Set-Cookie:", sep: str = "\r\n") -> str: ... + def output(self, attrs: Container[str] | None = None, header: str = "Set-Cookie:", sep: str = "\r\n") -> str: ... __str__ = output - def js_output(self, attrs: list[str] | None = None) -> str: ... - def load(self, rawdata: _DataType) -> None: ... + def js_output(self, attrs: Container[str] | None = None) -> str: ... + def load(self, rawdata: str | SupportsItems[str, str | Morsel[Any]]) -> None: ... def __setitem__(self, key: str, value: str | Morsel[_T]) -> None: ... class SimpleCookie(BaseCookie[str]): ... diff --git a/mypy/typeshed/stdlib/http/server.pyi b/mypy/typeshed/stdlib/http/server.pyi index 2c1a374331bcc..88cd2469cf115 100644 --- a/mypy/typeshed/stdlib/http/server.pyi +++ b/mypy/typeshed/stdlib/http/server.pyi @@ -10,18 +10,11 @@ from ssl import Purpose, SSLContext from typing import Any, AnyStr, BinaryIO, ClassVar, Protocol, type_check_only from typing_extensions import Self, deprecated +__all__ = ["HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler", "SimpleHTTPRequestHandler"] +if sys.version_info < (3, 15): + __all__ += ["CGIHTTPRequestHandler"] if sys.version_info >= (3, 14): - __all__ = [ - "HTTPServer", - "ThreadingHTTPServer", - "HTTPSServer", - "ThreadingHTTPSServer", - "BaseHTTPRequestHandler", - "SimpleHTTPRequestHandler", - "CGIHTTPRequestHandler", - ] -else: - __all__ = ["HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler", "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler"] + __all__ = ["HTTPSServer", "ThreadingHTTPSServer"] class HTTPServer(socketserver.TCPServer): server_name: str @@ -77,6 +70,8 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): protocol_version: str MessageClass: type responses: Mapping[int, tuple[str, str]] + if sys.version_info >= (3, 15): + default_content_type: str default_request_version: str # undocumented weekdayname: ClassVar[Sequence[str]] # undocumented monthname: ClassVar[Sequence[str | None]] # undocumented @@ -102,14 +97,26 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): if sys.version_info >= (3, 12): index_pages: ClassVar[tuple[str, ...]] directory: str - def __init__( - self, - request: socketserver._RequestType, - client_address: _socket._RetAddress, - server: socketserver.BaseServer, - *, - directory: StrPath | None = None, - ) -> None: ... + if sys.version_info >= (3, 15): + def __init__( + self, + request: socketserver._RequestType, + client_address: _socket._RetAddress, + server: socketserver.BaseServer, + *, + directory: StrPath | None = None, + extra_response_headers: Mapping[str, str] | None = None, + ) -> None: ... + else: + def __init__( + self, + request: socketserver._RequestType, + client_address: _socket._RetAddress, + server: socketserver.BaseServer, + *, + directory: StrPath | None = None, + ) -> None: ... + def do_GET(self) -> None: ... def do_HEAD(self) -> None: ... def send_head(self) -> io.BytesIO | BinaryIO | None: ... # undocumented @@ -120,7 +127,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): def executable(path: StrPath) -> bool: ... # undocumented -if sys.version_info >= (3, 13): +if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): cgi_directories: list[str] @@ -130,13 +137,3 @@ if sys.version_info >= (3, 13): def is_executable(self, path: StrPath) -> bool: ... # undocumented def is_python(self, path: StrPath) -> bool: ... # undocumented def run_cgi(self) -> None: ... # undocumented - -else: - class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): - cgi_directories: list[str] - have_fork: bool # undocumented - def do_POST(self) -> None: ... - def is_cgi(self) -> bool: ... # undocumented - def is_executable(self, path: StrPath) -> bool: ... # undocumented - def is_python(self, path: StrPath) -> bool: ... # undocumented - def run_cgi(self) -> None: ... # undocumented diff --git a/mypy/typeshed/stdlib/imaplib.pyi b/mypy/typeshed/stdlib/imaplib.pyi index 94b96f0a1283c..fd14e45ed725f 100644 --- a/mypy/typeshed/stdlib/imaplib.pyi +++ b/mypy/typeshed/stdlib/imaplib.pyi @@ -9,8 +9,8 @@ from re import Pattern from socket import socket as _socket from ssl import SSLContext, SSLSocket from types import TracebackType -from typing import IO, Any, Literal, SupportsAbs, SupportsInt, overload -from typing_extensions import Self, TypeAlias, deprecated +from typing import IO, Any, Literal, SupportsAbs, SupportsInt, TypeAlias, overload +from typing_extensions import Self, deprecated __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", "Int2AP", "ParseFlags", "Time2Internaldate", "IMAP4_SSL"] @@ -151,6 +151,7 @@ class IMAP4_SSL(IMAP4): ssl_context: None = None, timeout: float | None = None, ) -> None: ... + keyfile: StrOrBytesPath | None certfile: StrOrBytesPath | None sslobj: SSLSocket diff --git a/mypy/typeshed/stdlib/importlib/_abc.pyi b/mypy/typeshed/stdlib/importlib/_abc.pyi index 90ab340219172..e8c80c0447aed 100644 --- a/mypy/typeshed/stdlib/importlib/_abc.pyi +++ b/mypy/typeshed/stdlib/importlib/_abc.pyi @@ -4,17 +4,16 @@ from abc import ABCMeta from importlib.machinery import ModuleSpec from typing_extensions import deprecated -if sys.version_info >= (3, 10): - class Loader(metaclass=ABCMeta): - def load_module(self, fullname: str) -> types.ModuleType: ... - if sys.version_info < (3, 12): - @deprecated( - "Deprecated since Python 3.4; removed in Python 3.12. " - "The module spec is now used by the import machinery to generate a module repr." - ) - def module_repr(self, module: types.ModuleType) -> str: ... +class Loader(metaclass=ABCMeta): + def load_module(self, fullname: str) -> types.ModuleType: ... + if sys.version_info < (3, 12): + @deprecated( + "Deprecated since Python 3.4; removed in Python 3.12. " + "The module spec is now used by the import machinery to generate a module repr." + ) + def module_repr(self, module: types.ModuleType) -> str: ... - def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... - # Not defined on the actual class for backwards-compatibility reasons, - # but expected in new code. - def exec_module(self, module: types.ModuleType) -> None: ... + def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... + # Not defined on the actual class for backwards-compatibility reasons, + # but expected in new code. + def exec_module(self, module: types.ModuleType) -> None: ... diff --git a/mypy/typeshed/stdlib/importlib/abc.pyi b/mypy/typeshed/stdlib/importlib/abc.pyi index ef7761f7119b9..945b8d2080d1f 100644 --- a/mypy/typeshed/stdlib/importlib/abc.pyi +++ b/mypy/typeshed/stdlib/importlib/abc.pyi @@ -5,6 +5,7 @@ from _typeshed import ReadableBuffer, StrPath from abc import ABCMeta, abstractmethod from collections.abc import Iterator, Mapping, Sequence from importlib import _bootstrap_external +from importlib._abc import Loader as Loader from importlib.machinery import ModuleSpec from io import BufferedReader from typing import IO, Any, Literal, Protocol, overload, runtime_checkable @@ -25,17 +26,6 @@ if sys.version_info >= (3, 11): if sys.version_info < (3, 12): __all__ += ["Finder", "ResourceReader", "Traversable", "TraversableResources"] -if sys.version_info >= (3, 10): - from importlib._abc import Loader as Loader -else: - class Loader(metaclass=ABCMeta): - def load_module(self, fullname: str) -> types.ModuleType: ... - def module_repr(self, module: types.ModuleType) -> str: ... - def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ... - # Not defined on the actual class for backwards-compatibility reasons, - # but expected in new code. - def exec_module(self, module: types.ModuleType) -> None: ... - if sys.version_info < (3, 12): @deprecated("Deprecated since Python 3.3; removed in Python 3.12. Use `MetaPathFinder` or `PathEntryFinder` instead.") class Finder(metaclass=ABCMeta): ... @@ -67,47 +57,28 @@ class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLo def get_source(self, fullname: str) -> str | None: ... def path_stats(self, path: str) -> Mapping[str, Any]: ... -# The base classes differ starting in 3.10: -if sys.version_info >= (3, 10): - # Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol - class MetaPathFinder(metaclass=ABCMeta): - if sys.version_info < (3, 12): - @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `MetaPathFinder.find_spec()` instead.") - def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ... - - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec( - self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ..., / - ) -> ModuleSpec | None: ... - - class PathEntryFinder(metaclass=ABCMeta): - if sys.version_info < (3, 12): - @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `PathEntryFinder.find_spec()` instead.") - def find_module(self, fullname: str) -> Loader | None: ... - @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `find_spec()` instead.") - def find_loader(self, fullname: str) -> tuple[Loader | None, Sequence[str]]: ... - - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... - -else: - # Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol - class MetaPathFinder(Finder): +# Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol +class MetaPathFinder(metaclass=ABCMeta): + if sys.version_info < (3, 12): + @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `MetaPathFinder.find_spec()` instead.") def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ... - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec( - self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ..., / - ) -> ModuleSpec | None: ... - class PathEntryFinder(Finder): + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec( + self, fullname: str, path: Sequence[str] | None, target: types.ModuleType | None = ..., / + ) -> ModuleSpec | None: ... + +class PathEntryFinder(metaclass=ABCMeta): + if sys.version_info < (3, 12): + @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `PathEntryFinder.find_spec()` instead.") def find_module(self, fullname: str) -> Loader | None: ... + @deprecated("Deprecated since Python 3.4; removed in Python 3.12. Use `find_spec()` instead.") def find_loader(self, fullname: str) -> tuple[Loader | None, Sequence[str]]: ... - def invalidate_caches(self) -> None: ... - # Not defined on the actual class, but expected to exist. - def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... + + def invalidate_caches(self) -> None: ... + # Not defined on the actual class, but expected to exist. + def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ... class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader, metaclass=ABCMeta): name: str @@ -123,13 +94,8 @@ if sys.version_info < (3, 11): def open_resource(self, resource: str) -> IO[bytes]: ... @abstractmethod def resource_path(self, resource: str) -> str: ... - if sys.version_info >= (3, 10): - @abstractmethod - def is_resource(self, path: str) -> bool: ... - else: - @abstractmethod - def is_resource(self, name: str) -> bool: ... - + @abstractmethod + def is_resource(self, path: str) -> bool: ... @abstractmethod def contents(self) -> Iterator[str]: ... @@ -141,6 +107,7 @@ if sys.version_info < (3, 11): def is_file(self) -> bool: ... @abstractmethod def iterdir(self) -> Iterator[Traversable]: ... + if sys.version_info >= (3, 11): @abstractmethod def joinpath(self, *descendants: str) -> Traversable: ... @@ -157,15 +124,11 @@ if sys.version_info < (3, 11): @overload @abstractmethod def open(self, mode: Literal["rb"]) -> IO[bytes]: ... + @property @abstractmethod def name(self) -> str: ... - if sys.version_info >= (3, 10): - def __truediv__(self, child: str, /) -> Traversable: ... - else: - @abstractmethod - def __truediv__(self, child: str, /) -> Traversable: ... - + def __truediv__(self, child: str, /) -> Traversable: ... @abstractmethod def read_bytes(self) -> bytes: ... @abstractmethod diff --git a/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi b/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi index bb1b22f11a624..866fd969e2fe5 100644 --- a/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi +++ b/mypy/typeshed/stdlib/importlib/metadata/__init__.pyi @@ -5,13 +5,12 @@ import types from _collections_abc import dict_keys, dict_values from _typeshed import StrPath from collections.abc import Iterable, Iterator, Mapping -from email.message import Message from importlib.abc import MetaPathFinder +from importlib.metadata._meta import PackageMetadata as PackageMetadata, SimplePath from os import PathLike -from pathlib import Path from re import Pattern -from typing import Any, ClassVar, Generic, NamedTuple, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated, disjoint_base +from typing import Any, ClassVar, Generic, NamedTuple, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, deprecated, disjoint_base _T = TypeVar("_T") _KT = TypeVar("_KT") @@ -20,32 +19,32 @@ _VT = TypeVar("_VT") __all__ = [ "Distribution", "DistributionFinder", + "PackageMetadata", "PackageNotFoundError", "distribution", "distributions", "entry_points", "files", "metadata", + "packages_distributions", "requires", "version", ] -if sys.version_info >= (3, 10): - __all__ += ["PackageMetadata", "packages_distributions"] +if sys.version_info >= (3, 15): + __all__ += ["PackagePath", "MetadataNotFound", "SimplePath"] -if sys.version_info >= (3, 10): - from importlib.metadata._meta import PackageMetadata as PackageMetadata, SimplePath - def packages_distributions() -> Mapping[str, list[str]]: ... +_SimplePath: TypeAlias = SimplePath - _SimplePath: TypeAlias = SimplePath - -else: - _SimplePath: TypeAlias = Path +def packages_distributions() -> Mapping[str, list[str]]: ... class PackageNotFoundError(ModuleNotFoundError): @property def name(self) -> str: ... # type: ignore[override] +if sys.version_info >= (3, 15): + class MetadataNotFound(FileNotFoundError): ... + if sys.version_info >= (3, 13): _EntryPointBase = object elif sys.version_info >= (3, 11): @@ -104,19 +103,17 @@ else: def module(self) -> str: ... @property def attr(self) -> str: ... - if sys.version_info >= (3, 10): - dist: ClassVar[Distribution | None] - def matches( - self, - *, - name: str = ..., - value: str = ..., - group: str = ..., - module: str = ..., - attr: str = ..., - extras: list[str] = ..., - ) -> bool: ... # undocumented - + dist: ClassVar[Distribution | None] + def matches( + self, + *, + name: str = ..., + value: str = ..., + group: str = ..., + module: str = ..., + attr: str = ..., + extras: list[str] = ..., + ) -> bool: ... # undocumented def __hash__(self) -> int: ... def __iter__(self) -> Iterator[Any]: ... # result of iter((str, Self)), really @@ -139,7 +136,7 @@ if sys.version_info >= (3, 12): @property def groups(self) -> set[str]: ... -elif sys.version_info >= (3, 10): +else: class DeprecatedList(list[_T]): __slots__ = () @@ -162,15 +159,17 @@ elif sys.version_info >= (3, 10): @property def groups(self) -> set[str]: ... -if sys.version_info >= (3, 10) and sys.version_info < (3, 12): +if sys.version_info < (3, 12): class Deprecated(Generic[_KT, _VT]): def __getitem__(self, name: _KT) -> _VT: ... + @overload def get(self, name: _KT, default: None = None) -> _VT | None: ... @overload def get(self, name: _KT, default: _VT) -> _VT: ... @overload def get(self, name: _KT, default: _T) -> _VT | _T: ... + def __iter__(self) -> Iterator[_KT]: ... def __contains__(self, *args: object) -> bool: ... def keys(self) -> dict_keys[_KT, _VT]: ... @@ -184,6 +183,7 @@ if sys.version_info >= (3, 10) and sys.version_info < (3, 12): def groups(self) -> set[str]: ... @property def names(self) -> set[str]: ... + @overload def select(self) -> Self: ... @overload @@ -212,7 +212,9 @@ class FileHash: value: str def __init__(self, spec: str) -> None: ... -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 15): + _distribution_parent = abc.ABC +elif sys.version_info >= (3, 12): class DeprecatedNonAbstract: ... _distribution_parent = DeprecatedNonAbstract else: @@ -225,6 +227,7 @@ class Distribution(_distribution_parent): def locate_file(self, path: StrPath) -> _SimplePath: ... @classmethod def from_name(cls, name: str) -> Distribution: ... + @overload @classmethod def discover(cls, *, context: DistributionFinder.Context) -> Iterable[Distribution]: ... @@ -233,29 +236,21 @@ class Distribution(_distribution_parent): def discover( cls, *, context: None = None, name: str | None = ..., path: list[str] = ..., **kwargs: Any ) -> Iterable[Distribution]: ... + @staticmethod def at(path: StrPath) -> PathDistribution: ... - - if sys.version_info >= (3, 10): - @property - def metadata(self) -> PackageMetadata: ... - @property - def entry_points(self) -> EntryPoints: ... - else: - @property - def metadata(self) -> Message: ... - @property - def entry_points(self) -> list[EntryPoint]: ... - + @property + def metadata(self) -> PackageMetadata: ... + @property + def entry_points(self) -> EntryPoints: ... @property def version(self) -> str: ... @property def files(self) -> list[PackagePath] | None: ... @property def requires(self) -> list[str] | None: ... - if sys.version_info >= (3, 10): - @property - def name(self) -> str: ... + @property + def name(self) -> str: ... if sys.version_info >= (3, 13): @property def origin(self) -> types.SimpleNamespace | None: ... @@ -276,7 +271,7 @@ class MetadataPathFinder(DistributionFinder): if sys.version_info >= (3, 11): @classmethod def invalidate_caches(cls) -> None: ... - elif sys.version_info >= (3, 10): + else: # Yes, this is an instance method that has a parameter named "cls" def invalidate_caches(cls) -> None: ... @@ -287,6 +282,7 @@ class PathDistribution(Distribution): def locate_file(self, path: StrPath) -> _SimplePath: ... def distribution(distribution_name: str) -> Distribution: ... + @overload def distributions(*, context: DistributionFinder.Context) -> Iterable[Distribution]: ... @overload @@ -294,18 +290,14 @@ def distributions( *, context: None = None, name: str | None = ..., path: list[str] = ..., **kwargs: Any ) -> Iterable[Distribution]: ... -if sys.version_info >= (3, 10): - def metadata(distribution_name: str) -> PackageMetadata: ... - -else: - def metadata(distribution_name: str) -> Message: ... +def metadata(distribution_name: str) -> PackageMetadata: ... if sys.version_info >= (3, 12): def entry_points( *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ... ) -> EntryPoints: ... -elif sys.version_info >= (3, 10): +else: @overload def entry_points() -> SelectableGroups: ... @overload @@ -313,9 +305,6 @@ elif sys.version_info >= (3, 10): *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ... ) -> EntryPoints: ... -else: - def entry_points() -> dict[str, list[EntryPoint]]: ... - def version(distribution_name: str) -> str: ... def files(distribution_name: str) -> list[PackagePath] | None: ... def requires(distribution_name: str) -> list[str] | None: ... diff --git a/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi b/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi index 9f791dab254fd..b9bad7b8a6b0e 100644 --- a/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi +++ b/mypy/typeshed/stdlib/importlib/metadata/_meta.pyi @@ -15,10 +15,12 @@ class PackageMetadata(Protocol): def __iter__(self) -> Iterator[str]: ... @property def json(self) -> dict[str, str | list[str]]: ... + @overload def get_all(self, name: str, failobj: None = None) -> list[Any] | None: ... @overload def get_all(self, name: str, failobj: _T) -> list[Any] | _T: ... + if sys.version_info >= (3, 12): @overload def get(self, name: str, failobj: None = None) -> str | None: ... diff --git a/mypy/typeshed/stdlib/importlib/readers.pyi b/mypy/typeshed/stdlib/importlib/readers.pyi index 0e7f7ce165c3d..50f06ea5434d5 100644 --- a/mypy/typeshed/stdlib/importlib/readers.pyi +++ b/mypy/typeshed/stdlib/importlib/readers.pyi @@ -7,66 +7,63 @@ import sys import zipfile from _typeshed import StrPath from collections.abc import Iterable, Iterator +from importlib._bootstrap_external import FileLoader from io import BufferedReader from typing import Literal, NoReturn, TypeVar from typing_extensions import Never - -if sys.version_info >= (3, 10): - from importlib._bootstrap_external import FileLoader - from zipimport import zipimporter +from zipimport import zipimporter if sys.version_info >= (3, 11): from importlib.resources import abc else: from importlib import abc -if sys.version_info >= (3, 10): - if sys.version_info >= (3, 11): - __all__ = ["FileReader", "ZipReader", "MultiplexedPath", "NamespaceReader"] +if sys.version_info >= (3, 11): + __all__ = ["FileReader", "ZipReader", "MultiplexedPath", "NamespaceReader"] - if sys.version_info < (3, 11): - _T = TypeVar("_T") +if sys.version_info < (3, 11): + _T = TypeVar("_T") - def remove_duplicates(items: Iterable[_T]) -> Iterator[_T]: ... + def remove_duplicates(items: Iterable[_T]) -> Iterator[_T]: ... - class FileReader(abc.TraversableResources): - path: pathlib.Path - def __init__(self, loader: FileLoader) -> None: ... - def resource_path(self, resource: StrPath) -> str: ... - def files(self) -> pathlib.Path: ... +class FileReader(abc.TraversableResources): + path: pathlib.Path + def __init__(self, loader: FileLoader) -> None: ... + def resource_path(self, resource: StrPath) -> str: ... + def files(self) -> pathlib.Path: ... - class ZipReader(abc.TraversableResources): - prefix: str - archive: str - def __init__(self, loader: zipimporter, module: str) -> None: ... - def open_resource(self, resource: str) -> BufferedReader: ... - def is_resource(self, path: StrPath) -> bool: ... - def files(self) -> zipfile.Path: ... +class ZipReader(abc.TraversableResources): + prefix: str + archive: str + def __init__(self, loader: zipimporter, module: str) -> None: ... + def open_resource(self, resource: str) -> BufferedReader: ... + def is_resource(self, path: StrPath) -> bool: ... + def files(self) -> zipfile.Path: ... - class MultiplexedPath(abc.Traversable): - def __init__(self, *paths: abc.Traversable) -> None: ... - def iterdir(self) -> Iterator[abc.Traversable]: ... - def read_bytes(self) -> NoReturn: ... - def read_text(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] - def is_dir(self) -> Literal[True]: ... - def is_file(self) -> Literal[False]: ... +class MultiplexedPath(abc.Traversable): + def __init__(self, *paths: abc.Traversable) -> None: ... + def iterdir(self) -> Iterator[abc.Traversable]: ... + def read_bytes(self) -> NoReturn: ... + def read_text(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] + def is_dir(self) -> Literal[True]: ... + def is_file(self) -> Literal[False]: ... - if sys.version_info >= (3, 12): - def joinpath(self, *descendants: StrPath) -> abc.Traversable: ... - elif sys.version_info >= (3, 11): - def joinpath(self, child: StrPath) -> abc.Traversable: ... # type: ignore[override] - else: - def joinpath(self, child: str) -> abc.Traversable: ... + if sys.version_info >= (3, 12): + def joinpath(self, *descendants: StrPath) -> abc.Traversable: ... + elif sys.version_info >= (3, 11): + def joinpath(self, child: StrPath) -> abc.Traversable: ... # type: ignore[override] + else: + def joinpath(self, child: str) -> abc.Traversable: ... - if sys.version_info < (3, 12): - __truediv__ = joinpath + if sys.version_info < (3, 12): + __truediv__ = joinpath - def open(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] - @property - def name(self) -> str: ... + def open(self, *args: Never, **kwargs: Never) -> NoReturn: ... # type: ignore[override] + @property + def name(self) -> str: ... - class NamespaceReader(abc.TraversableResources): - path: MultiplexedPath - def __init__(self, namespace_path: Iterable[str]) -> None: ... - def resource_path(self, resource: str) -> str: ... - def files(self) -> MultiplexedPath: ... +class NamespaceReader(abc.TraversableResources): + path: MultiplexedPath + def __init__(self, namespace_path: Iterable[str]) -> None: ... + def resource_path(self, resource: str) -> str: ... + def files(self) -> MultiplexedPath: ... diff --git a/mypy/typeshed/stdlib/importlib/resources/__init__.pyi b/mypy/typeshed/stdlib/importlib/resources/__init__.pyi index 28adc37da4a42..5e58ddf310c93 100644 --- a/mypy/typeshed/stdlib/importlib/resources/__init__.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/__init__.pyi @@ -4,8 +4,8 @@ from collections.abc import Iterator from contextlib import AbstractContextManager from pathlib import Path from types import ModuleType -from typing import Any, BinaryIO, Literal, TextIO -from typing_extensions import TypeAlias, deprecated +from typing import Any, BinaryIO, Literal, TextIO, TypeAlias +from typing_extensions import deprecated if sys.version_info >= (3, 11): from importlib.resources.abc import Traversable @@ -19,6 +19,7 @@ else: __all__ = [ "Package", + "ResourceReader", "as_file", "contents", "files", @@ -30,9 +31,6 @@ __all__ = [ "read_text", ] -if sys.version_info >= (3, 10): - __all__ += ["ResourceReader"] - if sys.version_info < (3, 13): __all__ += ["Resource"] @@ -64,11 +62,8 @@ else: def read_text(package: Package, resource: Resource, encoding: str = "utf-8", errors: str = "strict") -> str: ... def path(package: Package, resource: Resource) -> AbstractContextManager[Path, Literal[False]]: ... def is_resource(package: Package, name: str) -> bool: ... - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11. Use `files(anchor).iterdir()`.") - def contents(package: Package) -> Iterator[str]: ... - else: - def contents(package: Package) -> Iterator[str]: ... + @deprecated("Deprecated since Python 3.11. Use `files(anchor).iterdir()`.") + def contents(package: Package) -> Iterator[str]: ... if sys.version_info >= (3, 11): from importlib.resources._common import as_file as as_file @@ -82,5 +77,5 @@ else: if sys.version_info >= (3, 11): from importlib.resources.abc import ResourceReader as ResourceReader -elif sys.version_info >= (3, 10): +else: from importlib.abc import ResourceReader as ResourceReader diff --git a/mypy/typeshed/stdlib/importlib/resources/_common.pyi b/mypy/typeshed/stdlib/importlib/resources/_common.pyi index 11a93ca82d8df..447cc1ea33b89 100644 --- a/mypy/typeshed/stdlib/importlib/resources/_common.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/_common.pyi @@ -7,8 +7,8 @@ if sys.version_info >= (3, 11): from contextlib import AbstractContextManager from importlib.resources.abc import ResourceReader, Traversable from pathlib import Path - from typing import Literal, overload - from typing_extensions import TypeAlias, deprecated + from typing import Literal, TypeAlias, overload + from typing_extensions import deprecated Package: TypeAlias = str | types.ModuleType @@ -18,6 +18,7 @@ if sys.version_info >= (3, 11): def package_to_anchor( func: Callable[[Anchor | None], Traversable], ) -> Callable[[Anchor | None, Anchor | None], Traversable]: ... + @overload def files(anchor: Anchor | None = None) -> Traversable: ... @overload diff --git a/mypy/typeshed/stdlib/importlib/resources/_functional.pyi b/mypy/typeshed/stdlib/importlib/resources/_functional.pyi index 71e01bcd3d5ec..cfd15dc87ce85 100644 --- a/mypy/typeshed/stdlib/importlib/resources/_functional.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/_functional.pyi @@ -12,19 +12,23 @@ if sys.version_info >= (3, 13): from typing_extensions import Unpack, deprecated def open_binary(anchor: Anchor, *path_names: StrPath) -> BinaryIO: ... + @overload def open_text( anchor: Anchor, *path_names: Unpack[tuple[StrPath]], encoding: str | None = "utf-8", errors: str | None = "strict" ) -> TextIOWrapper: ... @overload def open_text(anchor: Anchor, *path_names: StrPath, encoding: str | None, errors: str | None = "strict") -> TextIOWrapper: ... + def read_binary(anchor: Anchor, *path_names: StrPath) -> bytes: ... + @overload def read_text( anchor: Anchor, *path_names: Unpack[tuple[StrPath]], encoding: str | None = "utf-8", errors: str | None = "strict" ) -> str: ... @overload def read_text(anchor: Anchor, *path_names: StrPath, encoding: str | None, errors: str | None = "strict") -> str: ... + def path(anchor: Anchor, *path_names: StrPath) -> AbstractContextManager[Path, Literal[False]]: ... def is_resource(anchor: Anchor, *path_names: StrPath) -> bool: ... @deprecated("Deprecated since Python 3.11. Use `files(anchor).iterdir()`.") diff --git a/mypy/typeshed/stdlib/importlib/resources/abc.pyi b/mypy/typeshed/stdlib/importlib/resources/abc.pyi index 477339ea74291..20a4b1fc5dfef 100644 --- a/mypy/typeshed/stdlib/importlib/resources/abc.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/abc.pyi @@ -38,14 +38,20 @@ if sys.version_info >= (3, 11): @overload @abstractmethod def open(self, mode: Literal["rb"]) -> IO[bytes]: ... + @property @abstractmethod def name(self) -> str: ... def __truediv__(self, child: StrPath, /) -> Traversable: ... @abstractmethod def read_bytes(self) -> bytes: ... - @abstractmethod - def read_text(self, encoding: str | None = None) -> str: ... + + if sys.version_info >= (3, 15): + @abstractmethod + def read_text(self, encoding: str | None = None, errors: str | None = None) -> str: ... + else: + @abstractmethod + def read_text(self, encoding: str | None = None) -> str: ... class TraversableResources(ResourceReader): @abstractmethod diff --git a/mypy/typeshed/stdlib/importlib/resources/simple.pyi b/mypy/typeshed/stdlib/importlib/resources/simple.pyi index 946987c7312f9..ef9882056a9c2 100644 --- a/mypy/typeshed/stdlib/importlib/resources/simple.pyi +++ b/mypy/typeshed/stdlib/importlib/resources/simple.pyi @@ -27,6 +27,7 @@ if sys.version_info >= (3, 11): def __init__(self, parent: ResourceContainer, name: str) -> None: ... def is_file(self) -> Literal[True]: ... def is_dir(self) -> Literal[False]: ... + @overload def open( self, @@ -41,6 +42,7 @@ if sys.version_info >= (3, 11): def open(self, mode: Literal["rb"]) -> BinaryIO: ... @overload def open(self, mode: str) -> IO[Any]: ... + def joinpath(self, name: Never) -> NoReturn: ... # type: ignore[override] class ResourceContainer(Traversable, metaclass=abc.ABCMeta): diff --git a/mypy/typeshed/stdlib/importlib/util.pyi b/mypy/typeshed/stdlib/importlib/util.pyi index 577d3a667eca8..785ba6b9a08f0 100644 --- a/mypy/typeshed/stdlib/importlib/util.pyi +++ b/mypy/typeshed/stdlib/importlib/util.pyi @@ -13,8 +13,8 @@ from importlib._bootstrap_external import ( ) from importlib.abc import Loader from types import TracebackType -from typing import Literal -from typing_extensions import ParamSpec, Self, deprecated +from typing import Literal, ParamSpec +from typing_extensions import Self, deprecated _P = ParamSpec("_P") diff --git a/mypy/typeshed/stdlib/inspect.pyi b/mypy/typeshed/stdlib/inspect.pyi index c48d63bc4f322..1bd2b9dee325b 100644 --- a/mypy/typeshed/stdlib/inspect.pyi +++ b/mypy/typeshed/stdlib/inspect.pyi @@ -3,7 +3,6 @@ import enum import sys import types from _typeshed import AnnotationForm, StrPath -from collections import OrderedDict from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Generator, Mapping, Sequence, Set as AbstractSet from types import ( AsyncGeneratorType, @@ -25,8 +24,21 @@ from types import ( TracebackType, WrapperDescriptorType, ) -from typing import Any, ClassVar, Final, Literal, NamedTuple, Protocol, TypeVar, overload, type_check_only -from typing_extensions import ParamSpec, Self, TypeAlias, TypeGuard, TypeIs, deprecated, disjoint_base +from typing import ( + Any, + ClassVar, + Final, + Literal, + NamedTuple, + ParamSpec, + Protocol, + TypeAlias, + TypeGuard, + TypeVar, + overload, + type_check_only, +) +from typing_extensions import Never, Self, TypeIs, deprecated, disjoint_base if sys.version_info >= (3, 14): from annotationlib import Format @@ -183,8 +195,8 @@ if sys.version_info >= (3, 14): modulesbyfile: dict[str, Any] -_GetMembersPredicateTypeGuard: TypeAlias = Callable[[Any], TypeGuard[_T]] -_GetMembersPredicateTypeIs: TypeAlias = Callable[[Any], TypeIs[_T]] +_GetMembersPredicateTypeGuard = Callable[[Any], TypeGuard[_T]] +_GetMembersPredicateTypeIs = Callable[[Any], TypeIs[_T]] _GetMembersPredicate: TypeAlias = Callable[[Any], bool] _GetMembersReturn: TypeAlias = list[tuple[str, _T]] @@ -205,7 +217,7 @@ if sys.version_info >= (3, 11): def getmodulename(path: StrPath) -> str | None: ... def ismodule(object: object) -> TypeIs[ModuleType]: ... -def isclass(object: object) -> TypeIs[type[Any]]: ... +def isclass(object: object) -> TypeIs[type[object]]: ... def ismethod(object: object) -> TypeIs[MethodType]: ... if sys.version_info >= (3, 14): @@ -223,6 +235,7 @@ def isgeneratorfunction(obj: Callable[..., Generator[Any, Any, Any]]) -> bool: . def isgeneratorfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, GeneratorType[Any, Any, Any]]]: ... @overload def isgeneratorfunction(obj: object) -> TypeGuard[Callable[..., GeneratorType[Any, Any, Any]]]: ... + @overload def iscoroutinefunction(obj: Callable[..., Coroutine[Any, Any, Any]]) -> bool: ... @overload @@ -231,15 +244,18 @@ def iscoroutinefunction(obj: Callable[_P, Awaitable[_T]]) -> TypeGuard[Callable[ def iscoroutinefunction(obj: Callable[_P, object]) -> TypeGuard[Callable[_P, CoroutineType[Any, Any, Any]]]: ... @overload def iscoroutinefunction(obj: object) -> TypeGuard[Callable[..., CoroutineType[Any, Any, Any]]]: ... -def isgenerator(object: object) -> TypeIs[GeneratorType[Any, Any, Any]]: ... + +def isgenerator(object: object) -> TypeIs[GeneratorType[object, Never, object]]: ... def iscoroutine(object: object) -> TypeIs[CoroutineType[Any, Any, Any]]: ... def isawaitable(object: object) -> TypeIs[Awaitable[Any]]: ... + @overload def isasyncgenfunction(obj: Callable[..., AsyncGenerator[Any, Any]]) -> bool: ... @overload def isasyncgenfunction(obj: Callable[_P, Any]) -> TypeGuard[Callable[_P, AsyncGeneratorType[Any, Any]]]: ... @overload def isasyncgenfunction(obj: object) -> TypeGuard[Callable[..., AsyncGeneratorType[Any, Any]]]: ... + @type_check_only class _SupportsSet(Protocol[_T_contra, _V_contra]): def __set__(self, instance: _T_contra, value: _V_contra, /) -> None: ... @@ -248,7 +264,7 @@ class _SupportsSet(Protocol[_T_contra, _V_contra]): class _SupportsDelete(Protocol[_T_contra]): def __delete__(self, instance: _T_contra, /) -> None: ... -def isasyncgen(object: object) -> TypeIs[AsyncGeneratorType[Any, Any]]: ... +def isasyncgen(object: object) -> TypeIs[AsyncGeneratorType[object, Never]]: ... def istraceback(object: object) -> TypeIs[TracebackType]: ... def isframe(object: object) -> TypeIs[FrameType]: ... def iscode(object: object) -> TypeIs[CodeType]: ... @@ -273,7 +289,7 @@ def ismethoddescriptor(object: object) -> TypeIs[MethodDescriptorType]: ... def ismemberdescriptor(object: object) -> TypeIs[MemberDescriptorType]: ... def isabstract(object: object) -> bool: ... def isgetsetdescriptor(object: object) -> TypeIs[GetSetDescriptorType]: ... -def isdatadescriptor(object: object) -> TypeIs[_SupportsSet[Any, Any] | _SupportsDelete[Any]]: ... +def isdatadescriptor(object: object) -> TypeIs[_SupportsSet[Never, Never] | _SupportsDelete[Never]]: ... # # Retrieving source code @@ -293,7 +309,13 @@ def getblock(lines: list[str]) -> list[str]: ... def getblock(lines: tuple[str, ...]) -> tuple[str, ...]: ... @overload def getblock(lines: Sequence[str]) -> Sequence[str]: ... -def getdoc(object: object) -> str | None: ... + +if sys.version_info >= (3, 15): + def getdoc(object: object, *, inherit_class_doc: bool = True, fallback_to_class_doc: bool = True) -> str | None: ... + +else: + def getdoc(object: object) -> str | None: ... + def getcomments(object: object) -> str | None: ... def getfile(object: _SourceObjectType) -> str: ... def getmodule(object: object, _filename: str | None = None) -> ModuleType | None: ... @@ -319,7 +341,7 @@ if sys.version_info >= (3, 14): annotation_format: Format = Format.VALUE, # noqa: Y011 ) -> Signature: ... -elif sys.version_info >= (3, 10): +else: def signature( obj: _IntrospectableCallable, *, @@ -329,9 +351,6 @@ elif sys.version_info >= (3, 10): eval_str: bool = False, ) -> Signature: ... -else: - def signature(obj: _IntrospectableCallable, *, follow_wrapped: bool = True) -> Signature: ... - class _void: ... class _empty: ... @@ -361,7 +380,7 @@ class Signature: eval_str: bool = False, annotation_format: Format = Format.VALUE, # noqa: Y011 ) -> Self: ... - elif sys.version_info >= (3, 10): + else: @classmethod def from_callable( cls, @@ -372,9 +391,7 @@ class Signature: locals: Mapping[str, Any] | None = None, eval_str: bool = False, ) -> Self: ... - else: - @classmethod - def from_callable(cls, obj: _IntrospectableCallable, *, follow_wrapped: bool = True) -> Self: ... + if sys.version_info >= (3, 14): def format(self, *, max_width: int | None = None, quote_annotation_strings: bool = True) -> str: ... elif sys.version_info >= (3, 13): @@ -385,7 +402,7 @@ class Signature: if sys.version_info >= (3, 14): from annotationlib import get_annotations as get_annotations -elif sys.version_info >= (3, 10): +else: def get_annotations( obj: Callable[..., object] | type[object] | ModuleType, # any callable, class, or module *, @@ -450,14 +467,14 @@ class Parameter: class BoundArguments: __slots__ = ("arguments", "_signature", "__weakref__") - arguments: OrderedDict[str, Any] + arguments: dict[str, Any] @property def args(self) -> tuple[Any, ...]: ... @property def kwargs(self) -> dict[str, Any]: ... @property def signature(self) -> Signature: ... - def __init__(self, signature: Signature, arguments: OrderedDict[str, Any]) -> None: ... + def __init__(self, signature: Signature, arguments: dict[str, Any]) -> None: ... def apply_defaults(self) -> None: ... def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] @@ -498,7 +515,11 @@ class FullArgSpec(NamedTuple): kwonlydefaults: dict[str, Any] | None annotations: dict[str, Any] -def getfullargspec(func: object) -> FullArgSpec: ... +if sys.version_info >= (3, 15): + def getfullargspec(func: object, *, annotation_format: Format = Format.VALUE) -> FullArgSpec: ... # noqa: Y011 + +else: + def getfullargspec(func: object) -> FullArgSpec: ... class ArgInfo(NamedTuple): args: list[str] diff --git a/mypy/typeshed/stdlib/ipaddress.pyi b/mypy/typeshed/stdlib/ipaddress.pyi index d09804cb93423..c514abfa569f9 100644 --- a/mypy/typeshed/stdlib/ipaddress.pyi +++ b/mypy/typeshed/stdlib/ipaddress.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Iterable, Iterator -from typing import Any, Final, Generic, Literal, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing import Any, Final, Generic, Literal, TypeAlias, TypeVar, overload +from typing_extensions import Self # Undocumented length constants IPV4LENGTH: Final = 32 @@ -235,7 +235,9 @@ def summarize_address_range(first: IPv6Address, last: IPv6Address) -> Iterator[I def summarize_address_range( first: IPv4Address | IPv6Address, last: IPv4Address | IPv6Address ) -> Iterator[IPv4Network] | Iterator[IPv6Network]: ... + def collapse_addresses(addresses: Iterable[_N]) -> Iterator[_N]: ... + @overload def get_mixed_type_key(obj: _A) -> tuple[int, _A]: ... @overload diff --git a/mypy/typeshed/stdlib/itertools.pyi b/mypy/typeshed/stdlib/itertools.pyi index 4713d62cc346f..fd7daf11e8086 100644 --- a/mypy/typeshed/stdlib/itertools.pyi +++ b/mypy/typeshed/stdlib/itertools.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import MaybeNone from collections.abc import Callable, Iterable, Iterator from types import GenericAlias -from typing import Any, Generic, Literal, SupportsComplex, SupportsFloat, SupportsIndex, SupportsInt, TypeVar, overload -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, Generic, Literal, SupportsComplex, SupportsFloat, SupportsIndex, SupportsInt, TypeAlias, TypeVar, overload +from typing_extensions import Self, disjoint_base _T = TypeVar("_T") _S = TypeVar("_S") @@ -23,7 +23,7 @@ _T10 = TypeVar("_T10") _Step: TypeAlias = SupportsFloat | SupportsInt | SupportsIndex | SupportsComplex -_Predicate: TypeAlias = Callable[[_T], object] +_Predicate = Callable[[_T], object] # Technically count can take anything that implements a number protocol and has an add method # but we can't enforce the add method @@ -35,6 +35,7 @@ class count(Iterator[_N]): def __new__(cls, start: _N, step: _Step = 1) -> count[_N]: ... @overload def __new__(cls, *, step: _N) -> count[_N]: ... + def __next__(self) -> _N: ... def __iter__(self) -> Self: ... @@ -50,6 +51,7 @@ class repeat(Iterator[_T]): def __new__(cls, object: _T) -> Self: ... @overload def __new__(cls, object: _T, times: int) -> Self: ... + def __next__(self) -> _T: ... def __iter__(self) -> Self: ... def __length_hint__(self) -> int: ... @@ -60,6 +62,7 @@ class accumulate(Iterator[_T]): def __new__(cls, iterable: Iterable[_T], func: None = None, *, initial: _T | None = None) -> Self: ... @overload def __new__(cls, iterable: Iterable[_S], func: Callable[[_T, _S], _T], *, initial: _T | None = None) -> Self: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T: ... @@ -97,6 +100,7 @@ class groupby(Iterator[tuple[_T_co, Iterator[_S_co]]], Generic[_T_co, _S_co]): def __new__(cls, iterable: Iterable[_T1], key: None = None) -> groupby[_T1, _T1]: ... @overload def __new__(cls, iterable: Iterable[_T1], key: Callable[[_T1], _T2]) -> groupby[_T2, _T1]: ... + def __iter__(self) -> Self: ... def __next__(self) -> tuple[_T_co, Iterator[_S_co]]: ... @@ -106,6 +110,7 @@ class islice(Iterator[_T]): def __new__(cls, iterable: Iterable[_T], stop: int | None, /) -> Self: ... @overload def __new__(cls, iterable: Iterable[_T], start: int | None, stop: int | None, step: int | None = 1, /) -> Self: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T: ... @@ -122,6 +127,7 @@ class takewhile(Iterator[_T]): def __next__(self) -> _T: ... def tee(iterable: Iterable[_T], n: int = 2, /) -> tuple[Iterator[_T], ...]: ... + @disjoint_base class zip_longest(Iterator[_T_co]): # one iterable (fillvalue doesn't matter) @@ -198,6 +204,7 @@ class zip_longest(Iterator[_T_co]): *iterables: Iterable[_T], fillvalue: _T, ) -> zip_longest[tuple[_T, ...]]: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... @@ -284,6 +291,7 @@ class product(Iterator[_T_co]): ) -> product[tuple[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10]]: ... @overload def __new__(cls, *iterables: Iterable[_T1], repeat: int = 1) -> product[tuple[_T1, ...]]: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... @@ -299,6 +307,7 @@ class permutations(Iterator[_T_co]): def __new__(cls, iterable: Iterable[_T], r: Literal[5]) -> permutations[tuple[_T, _T, _T, _T, _T]]: ... @overload def __new__(cls, iterable: Iterable[_T], r: int | None = None) -> permutations[tuple[_T, ...]]: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... @@ -314,6 +323,7 @@ class combinations(Iterator[_T_co]): def __new__(cls, iterable: Iterable[_T], r: Literal[5]) -> combinations[tuple[_T, _T, _T, _T, _T]]: ... @overload def __new__(cls, iterable: Iterable[_T], r: int) -> combinations[tuple[_T, ...]]: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... @@ -329,15 +339,15 @@ class combinations_with_replacement(Iterator[_T_co]): def __new__(cls, iterable: Iterable[_T], r: Literal[5]) -> combinations_with_replacement[tuple[_T, _T, _T, _T, _T]]: ... @overload def __new__(cls, iterable: Iterable[_T], r: int) -> combinations_with_replacement[tuple[_T, ...]]: ... + def __iter__(self) -> Self: ... def __next__(self) -> _T_co: ... -if sys.version_info >= (3, 10): - @disjoint_base - class pairwise(Iterator[_T_co]): - def __new__(cls, iterable: Iterable[_T], /) -> pairwise[tuple[_T, _T]]: ... - def __iter__(self) -> Self: ... - def __next__(self) -> _T_co: ... +@disjoint_base +class pairwise(Iterator[_T_co]): + def __new__(cls, iterable: Iterable[_T], /) -> pairwise[tuple[_T, _T]]: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _T_co: ... if sys.version_info >= (3, 12): @disjoint_base diff --git a/mypy/typeshed/stdlib/json/__init__.pyi b/mypy/typeshed/stdlib/json/__init__.pyi index 454a235ecf703..2342b29cb0c82 100644 --- a/mypy/typeshed/stdlib/json/__init__.pyi +++ b/mypy/typeshed/stdlib/json/__init__.pyi @@ -1,3 +1,4 @@ +import sys from _typeshed import SupportsRead, SupportsWrite from collections.abc import Callable from typing import Any, Literal @@ -36,28 +37,57 @@ def dump( sort_keys: bool = False, **kwds: Any, ) -> None: ... -def loads( - s: str | bytes | bytearray, - *, - cls: type[JSONDecoder] | None = None, - object_hook: Callable[[dict[Any, Any]], Any] | None = None, - parse_float: Callable[[str], Any] | None = None, - parse_int: Callable[[str], Any] | None = None, - parse_constant: Callable[[str], Any] | None = None, - object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, - **kwds: Any, -) -> Any: ... -def load( - fp: SupportsRead[str | bytes], - *, - cls: type[JSONDecoder] | None = None, - object_hook: Callable[[dict[Any, Any]], Any] | None = None, - parse_float: Callable[[str], Any] | None = None, - parse_int: Callable[[str], Any] | None = None, - parse_constant: Callable[[str], Any] | None = None, - object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, - **kwds: Any, -) -> Any: ... + +if sys.version_info >= (3, 15): + def loads( + s: str | bytes | bytearray, + *, + cls: type[JSONDecoder] | None = None, + object_hook: Callable[[dict[Any, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, + array_hook: Callable[[list[Any]], Any] | None = None, + **kwds: Any, + ) -> Any: ... + def load( + fp: SupportsRead[str | bytes], + *, + cls: type[JSONDecoder] | None = None, + object_hook: Callable[[dict[Any, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, + array_hook: Callable[[list[Any]], Any] | None = None, + **kwds: Any, + ) -> Any: ... + +else: + def loads( + s: str | bytes | bytearray, + *, + cls: type[JSONDecoder] | None = None, + object_hook: Callable[[dict[Any, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, + **kwds: Any, + ) -> Any: ... + def load( + fp: SupportsRead[str | bytes], + *, + cls: type[JSONDecoder] | None = None, + object_hook: Callable[[dict[Any, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + object_pairs_hook: Callable[[list[tuple[Any, Any]]], Any] | None = None, + **kwds: Any, + ) -> Any: ... + def detect_encoding( b: bytes | bytearray, ) -> Literal["utf-8", "utf-8-sig", "utf-16", "utf-16-be", "utf-16-le", "utf-32", "utf-32-be", "utf-32-le"]: ... # undocumented diff --git a/mypy/typeshed/stdlib/json/decoder.pyi b/mypy/typeshed/stdlib/json/decoder.pyi index 8debfe6cd65a9..1b09579fb0c41 100644 --- a/mypy/typeshed/stdlib/json/decoder.pyi +++ b/mypy/typeshed/stdlib/json/decoder.pyi @@ -1,3 +1,4 @@ +import sys from collections.abc import Callable from typing import Any @@ -12,21 +13,38 @@ class JSONDecodeError(ValueError): def __init__(self, msg: str, doc: str, pos: int) -> None: ... class JSONDecoder: + if sys.version_info >= (3, 15): + array_hook: Callable[[list[Any]], Any] | None object_hook: Callable[[dict[str, Any]], Any] parse_float: Callable[[str], Any] parse_int: Callable[[str], Any] parse_constant: Callable[[str], Any] strict: bool object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] - def __init__( - self, - *, - object_hook: Callable[[dict[str, Any]], Any] | None = None, - parse_float: Callable[[str], Any] | None = None, - parse_int: Callable[[str], Any] | None = None, - parse_constant: Callable[[str], Any] | None = None, - strict: bool = True, - object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] | None = None, - ) -> None: ... + if sys.version_info >= (3, 15): + def __init__( + self, + *, + object_hook: Callable[[dict[str, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + strict: bool = True, + object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] | None = None, + array_hook: Callable[[list[Any]], Any] | None = None, + ) -> None: ... + + else: + def __init__( + self, + *, + object_hook: Callable[[dict[str, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + strict: bool = True, + object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] | None = None, + ) -> None: ... + def decode(self, s: str, _w: Callable[..., Any] = ...) -> Any: ... # _w is undocumented def raw_decode(self, s: str, idx: int = 0) -> tuple[Any, int]: ... diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi index de8a874f434d0..3b1cbef7727d2 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/__init__.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import Any -from typing_extensions import TypeAlias +from typing import Any, TypeAlias from ..pytree import _RawNode from .grammar import Grammar diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/grammar.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/grammar.pyi index bef0a7922683b..5093422ae2364 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/grammar.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/grammar.pyi @@ -1,5 +1,6 @@ from _typeshed import StrPath -from typing_extensions import Self, TypeAlias +from typing import TypeAlias +from typing_extensions import Self _Label: TypeAlias = tuple[int, str | None] _DFA: TypeAlias = list[list[tuple[int, int]]] diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi index 320c5f018d43f..9befe9bf879d3 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/parse.pyi @@ -1,6 +1,6 @@ from _typeshed import Incomplete from collections.abc import Sequence -from typing_extensions import TypeAlias +from typing import TypeAlias from ..pytree import _NL, _RawNode from . import _Convert diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi index 5776d100d1da0..4b951e1489e15 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/pgen.pyi @@ -29,6 +29,7 @@ class ParserGenerator: def parse_atom(self) -> tuple[NFAState, NFAState]: ... def expect(self, type: int, value: str | None = None) -> str: ... def gettoken(self) -> None: ... + @overload def raise_error(self, msg: object) -> NoReturn: ... @overload diff --git a/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi b/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi index af54de1b51d33..76ff733163afe 100644 --- a/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pgen2/tokenize.pyi @@ -1,5 +1,5 @@ from collections.abc import Callable, Iterable, Iterator -from typing_extensions import TypeAlias +from typing import TypeAlias from .token import * diff --git a/mypy/typeshed/stdlib/lib2to3/pytree.pyi b/mypy/typeshed/stdlib/lib2to3/pytree.pyi index 51bdbc75e1421..045f7882721a1 100644 --- a/mypy/typeshed/stdlib/lib2to3/pytree.pyi +++ b/mypy/typeshed/stdlib/lib2to3/pytree.pyi @@ -1,8 +1,8 @@ from _typeshed import Incomplete, SupportsGetItem, SupportsLenAndGetItem, Unused from abc import abstractmethod from collections.abc import Iterable, Iterator, MutableSequence -from typing import ClassVar, Final -from typing_extensions import Self, TypeAlias +from typing import ClassVar, Final, TypeAlias +from typing_extensions import Self from .fixer_base import BaseFix from .pgen2.grammar import Grammar diff --git a/mypy/typeshed/stdlib/lib2to3/refactor.pyi b/mypy/typeshed/stdlib/lib2to3/refactor.pyi index c33347ede38fd..dfec28b758b5d 100644 --- a/mypy/typeshed/stdlib/lib2to3/refactor.pyi +++ b/mypy/typeshed/stdlib/lib2to3/refactor.pyi @@ -40,14 +40,17 @@ class RefactoringTool: ) -> None: ... def get_fixers(self) -> tuple[list[BaseFix], list[BaseFix]]: ... def log_error(self, msg: str, *args: Iterable[str], **kwargs: _ExcInfoType) -> NoReturn: ... + @overload def log_message(self, msg: object) -> None: ... @overload def log_message(self, msg: str, *args: object) -> None: ... + @overload def log_debug(self, msg: object) -> None: ... @overload def log_debug(self, msg: str, *args: object) -> None: ... + def print_output(self, old_text: str, new_text: str, filename: StrPath, equal: bool) -> None: ... def refactor(self, items: Iterable[str], write: bool = False, doctests_only: bool = False) -> None: ... def refactor_dir(self, dir_name: str, write: bool = False, doctests_only: bool = False) -> None: ... diff --git a/mypy/typeshed/stdlib/linecache.pyi b/mypy/typeshed/stdlib/linecache.pyi index 5379a21e7d123..f527e7084ced8 100644 --- a/mypy/typeshed/stdlib/linecache.pyi +++ b/mypy/typeshed/stdlib/linecache.pyi @@ -1,6 +1,5 @@ from collections.abc import Callable -from typing import Any -from typing_extensions import TypeAlias +from typing import Any, TypeAlias __all__ = ["getline", "clearcache", "checkcache", "lazycache"] diff --git a/mypy/typeshed/stdlib/locale.pyi b/mypy/typeshed/stdlib/locale.pyi index 80c39a532dc86..1be68ec516344 100644 --- a/mypy/typeshed/stdlib/locale.pyi +++ b/mypy/typeshed/stdlib/locale.pyi @@ -138,11 +138,8 @@ def getpreferredencoding(do_setlocale: bool = True) -> _str: ... def normalize(localename: _str) -> _str: ... if sys.version_info < (3, 13): - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11; removed in Python 3.13. Use `locale.setlocale(locale.LC_ALL, '')` instead.") - def resetlocale(category: int = ...) -> None: ... - else: - def resetlocale(category: int = ...) -> None: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13. Use `locale.setlocale(locale.LC_ALL, '')` instead.") + def resetlocale(category: int = ...) -> None: ... if sys.version_info < (3, 12): @deprecated("Deprecated since Python 3.7; removed in Python 3.12. Use `locale.format_string()` instead.") @@ -153,10 +150,7 @@ if sys.version_info < (3, 12): def format_string(f: _str, val: Any, grouping: bool = False, monetary: bool = False) -> _str: ... def currency(val: float | Decimal, symbol: bool = True, grouping: bool = False, international: bool = False) -> _str: ... def delocalize(string: _str) -> _str: ... - -if sys.version_info >= (3, 10): - def localize(string: _str, grouping: bool = False, monetary: bool = False) -> _str: ... - +def localize(string: _str, grouping: bool = False, monetary: bool = False) -> _str: ... def atof(string: _str, func: Callable[[_str], float] = ...) -> float: ... def atoi(string: _str) -> int: ... def str(val: float) -> _str: ... diff --git a/mypy/typeshed/stdlib/logging/__init__.pyi b/mypy/typeshed/stdlib/logging/__init__.pyi index 89c94816a906c..8e90a68d9453c 100644 --- a/mypy/typeshed/stdlib/logging/__init__.pyi +++ b/mypy/typeshed/stdlib/logging/__init__.pyi @@ -7,8 +7,8 @@ from re import Pattern from string import Template from time import struct_time from types import FrameType, GenericAlias, TracebackType -from typing import Any, ClassVar, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import Any, ClassVar, Final, Generic, Literal, Protocol, TextIO, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, deprecated __all__ = [ "BASIC_FORMAT", @@ -274,21 +274,15 @@ class Formatter: default_time_format: str default_msec_format: str | None - if sys.version_info >= (3, 10): - def __init__( - self, - fmt: str | None = None, - datefmt: str | None = None, - style: _FormatStyle = "%", - validate: bool = True, - *, - defaults: Mapping[str, Any] | None = None, - ) -> None: ... - else: - def __init__( - self, fmt: str | None = None, datefmt: str | None = None, style: _FormatStyle = "%", validate: bool = True - ) -> None: ... - + def __init__( + self, + fmt: str | None = None, + datefmt: str | None = None, + style: _FormatStyle = "%", + validate: bool = True, + *, + defaults: Mapping[str, Any] | None = None, + ) -> None: ... def format(self, record: LogRecord) -> str: ... def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: ... def formatException(self, ei: _SysExcInfoType) -> str: ... @@ -362,18 +356,12 @@ _L = TypeVar("_L", bound=Logger | LoggerAdapter[Any]) class LoggerAdapter(Generic[_L]): logger: _L manager: Manager # undocumented + extra: Mapping[str, object] | None if sys.version_info >= (3, 13): def __init__(self, logger: _L, extra: Mapping[str, object] | None = None, merge_extra: bool = False) -> None: ... - elif sys.version_info >= (3, 10): - def __init__(self, logger: _L, extra: Mapping[str, object] | None = None) -> None: ... else: - def __init__(self, logger: _L, extra: Mapping[str, object]) -> None: ... - - if sys.version_info >= (3, 10): - extra: Mapping[str, object] | None - else: - extra: Mapping[str, object] + def __init__(self, logger: _L, extra: Mapping[str, object] | None = None) -> None: ... if sys.version_info >= (3, 13): merge_extra: bool @@ -566,6 +554,7 @@ fatal = critical def disable(level: int = 50) -> None: ... def addLevelName(level: int, levelName: str) -> None: ... + @overload def getLevelName(level: int) -> str: ... @overload @@ -576,6 +565,7 @@ if sys.version_info >= (3, 11): def getLevelNamesMapping() -> dict[str, int]: ... def makeLogRecord(dict: Mapping[str, object]) -> LogRecord: ... + @overload # handlers is non-None def basicConfig( *, @@ -611,6 +601,7 @@ def basicConfig( handlers: None = None, force: bool | None = False, ) -> None: ... + def shutdown(handlerList: Sequence[Any] = ...) -> None: ... # handlerList is undocumented def setLoggerClass(klass: type[Logger]) -> None: ... def captureWarnings(capture: bool) -> None: ... @@ -623,10 +614,12 @@ _StreamT = TypeVar("_StreamT", bound=SupportsWrite[str]) class StreamHandler(Handler, Generic[_StreamT]): stream: _StreamT # undocumented terminator: str + @overload def __init__(self: StreamHandler[TextIO], stream: None = None) -> None: ... @overload def __init__(self: StreamHandler[_StreamT], stream: _StreamT) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780 + def setStream(self, stream: _StreamT) -> _StreamT | None: ... if sys.version_info >= (3, 11): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -663,11 +656,8 @@ class PercentStyle: # undocumented asctime_search: str validation_pattern: Pattern[str] _fmt: str - if sys.version_info >= (3, 10): - def __init__(self, fmt: str, *, defaults: Mapping[str, Any] | None = None) -> None: ... - else: - def __init__(self, fmt: str) -> None: ... + def __init__(self, fmt: str, *, defaults: Mapping[str, Any] | None = None) -> None: ... def usesTime(self) -> bool: ... def validate(self) -> None: ... def format(self, record: Any) -> str: ... diff --git a/mypy/typeshed/stdlib/logging/config.pyi b/mypy/typeshed/stdlib/logging/config.pyi index e362145516001..1c870edc80f8b 100644 --- a/mypy/typeshed/stdlib/logging/config.pyi +++ b/mypy/typeshed/stdlib/logging/config.pyi @@ -4,8 +4,8 @@ from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence from configparser import RawConfigParser from re import Pattern from threading import Thread -from typing import IO, Any, Final, Literal, SupportsIndex, TypedDict, overload, type_check_only -from typing_extensions import Required, TypeAlias, disjoint_base +from typing import IO, Any, Final, Literal, SupportsIndex, TypeAlias, TypedDict, overload, type_check_only +from typing_extensions import Required, disjoint_base from . import Filter, Filterer, Formatter, Handler, Logger, _FilterType, _FormatStyle, _Level @@ -64,22 +64,12 @@ class _DictConfigArgs(TypedDict, total=False): # Also accept a TypedDict type, to allow callers to use TypedDict # types, and for somewhat stricter type checking of dict literals. def dictConfig(config: _DictConfigArgs | dict[str, Any]) -> None: ... - -if sys.version_info >= (3, 10): - def fileConfig( - fname: StrOrBytesPath | IO[str] | RawConfigParser, - defaults: Mapping[str, str] | None = None, - disable_existing_loggers: bool = True, - encoding: str | None = None, - ) -> None: ... - -else: - def fileConfig( - fname: StrOrBytesPath | IO[str] | RawConfigParser, - defaults: Mapping[str, str] | None = None, - disable_existing_loggers: bool = True, - ) -> None: ... - +def fileConfig( + fname: StrOrBytesPath | IO[str] | RawConfigParser, + defaults: Mapping[str, str] | None = None, + disable_existing_loggers: bool = True, + encoding: str | None = None, +) -> None: ... def valid_ident(s: str) -> Literal[True]: ... # undocumented def listen(port: int = 9030, verify: Callable[[bytes], bytes | None] | None = None) -> Thread: ... def stopListening() -> None: ... @@ -98,6 +88,7 @@ class ConvertingList(list[Any], ConvertingMixin): # undocumented def __getitem__(self, key: SupportsIndex) -> Any: ... @overload def __getitem__(self, key: slice[SupportsIndex | None]) -> Any: ... + def pop(self, idx: SupportsIndex = -1) -> Any: ... if sys.version_info >= (3, 12): diff --git a/mypy/typeshed/stdlib/lzma.pyi b/mypy/typeshed/stdlib/lzma.pyi index b7ef607b75cbf..656ecb168c956 100644 --- a/mypy/typeshed/stdlib/lzma.pyi +++ b/mypy/typeshed/stdlib/lzma.pyi @@ -36,8 +36,8 @@ from _lzma import ( ) from _typeshed import ReadableBuffer, StrOrBytesPath from io import TextIOWrapper -from typing import IO, Literal, overload -from typing_extensions import Self, TypeAlias +from typing import IO, Literal, TypeAlias, overload +from typing_extensions import Self if sys.version_info >= (3, 14): from compression._common._streams import BaseStream @@ -172,6 +172,7 @@ def open( errors: str | None = None, newline: str | None = None, ) -> LZMAFile | TextIOWrapper: ... + def compress( data: ReadableBuffer, format: int = 1, check: int = -1, preset: int | None = None, filters: _FilterChain | None = None ) -> bytes: ... diff --git a/mypy/typeshed/stdlib/mailbox.pyi b/mypy/typeshed/stdlib/mailbox.pyi index 89bd998b4dfeb..961c0f5e81f7a 100644 --- a/mypy/typeshed/stdlib/mailbox.pyi +++ b/mypy/typeshed/stdlib/mailbox.pyi @@ -1,13 +1,12 @@ import email.message import io import sys -from _typeshed import StrPath, SupportsNoArgReadline, SupportsRead +from _typeshed import StrPath, SupportsItems, SupportsNoArgReadline, SupportsRead, SupportsWrite, Unused from abc import ABCMeta, abstractmethod from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence -from email._policybase import _MessageT from types import GenericAlias, TracebackType -from typing import IO, Any, AnyStr, Generic, Literal, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, Generic, Literal, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self __all__ = [ "Mailbox", @@ -34,25 +33,49 @@ _T = TypeVar("_T") @type_check_only class _SupportsReadAndReadline(SupportsRead[bytes], SupportsNoArgReadline[bytes], Protocol): ... +# As opposed to _MessageT_co in email._policybase, this type is bound to +# mailbox.Message instead of email.message.Message. +_MessageT_co = TypeVar("_MessageT_co", bound=Message, default=Message, covariant=True) + _MessageData: TypeAlias = email.message.Message | bytes | str | io.StringIO | _SupportsReadAndReadline @type_check_only class _HasIteritems(Protocol): def iteritems(self) -> Iterator[tuple[str, _MessageData]]: ... -@type_check_only -class _HasItems(Protocol): - def items(self) -> Iterator[tuple[str, _MessageData]]: ... - linesep: bytes -class Mailbox(Generic[_MessageT]): +# Common interface for get_file() return types. +@type_check_only +class _GetFileReturn(Protocol): + def __iter__(self) -> Iterator[bytes]: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, / + ) -> bool | None: ... + def read(self, size: int | None = None, /) -> bytes: ... + def read1(self, size: int | None = None, /) -> bytes: ... + def readline(self, size: int | None = None, /) -> bytes: ... + def readlines(self, sizehint: int | None = None, /) -> list[bytes]: ... + def tell(self) -> int: ... + def seek(self, offset: int, whence: int = 0, /) -> object: ... + def close(self) -> object: ... + def readable(self) -> bool: ... + def writable(self) -> bool: ... + def seekable(self) -> bool: ... + def flush(self) -> object: ... + @property + def closed(self) -> bool: ... + +class Mailbox(Generic[_MessageT_co]): _path: str # undocumented - _factory: Callable[[IO[Any]], _MessageT] | None # undocumented + _factory: Callable[[_GetFileReturn], _MessageT_co] | None # undocumented + @overload - def __init__(self, path: StrPath, factory: Callable[[IO[Any]], _MessageT], create: bool = True) -> None: ... + def __init__(self, path: StrPath, factory: Callable[[_GetFileReturn], _MessageT_co], create: bool = True) -> None: ... @overload def __init__(self, path: StrPath, factory: None = None, create: bool = True) -> None: ... + @abstractmethod def add(self, message: _MessageData) -> str: ... @abstractmethod @@ -61,38 +84,43 @@ class Mailbox(Generic[_MessageT]): def discard(self, key: str) -> None: ... @abstractmethod def __setitem__(self, key: str, message: _MessageData) -> None: ... + @overload - def get(self, key: str, default: None = None) -> _MessageT | None: ... + def get(self, key: str, default: None = None) -> _MessageT_co | None: ... @overload - def get(self, key: str, default: _T) -> _MessageT | _T: ... - def __getitem__(self, key: str) -> _MessageT: ... + def get(self, key: str, default: _T) -> _MessageT_co | _T: ... + + def __getitem__(self, key: str) -> _MessageT_co: ... @abstractmethod - def get_message(self, key: str) -> _MessageT: ... + def get_message(self, key: str) -> _MessageT_co: ... def get_string(self, key: str) -> str: ... @abstractmethod def get_bytes(self, key: str) -> bytes: ... - # As '_ProxyFile' doesn't implement the full IO spec, and BytesIO is incompatible with it, get_file return is Any here @abstractmethod - def get_file(self, key: str) -> Any: ... + def get_file(self, key: str) -> _GetFileReturn: ... @abstractmethod def iterkeys(self) -> Iterator[str]: ... def keys(self) -> list[str]: ... - def itervalues(self) -> Iterator[_MessageT]: ... - def __iter__(self) -> Iterator[_MessageT]: ... - def values(self) -> list[_MessageT]: ... - def iteritems(self) -> Iterator[tuple[str, _MessageT]]: ... - def items(self) -> list[tuple[str, _MessageT]]: ... + def itervalues(self) -> Iterator[_MessageT_co]: ... + def __iter__(self) -> Iterator[_MessageT_co]: ... + def values(self) -> list[_MessageT_co]: ... + def iteritems(self) -> Iterator[tuple[str, _MessageT_co]]: ... + def items(self) -> list[tuple[str, _MessageT_co]]: ... @abstractmethod def __contains__(self, key: str) -> bool: ... @abstractmethod def __len__(self) -> int: ... def clear(self) -> None: ... + @overload - def pop(self, key: str, default: None = None) -> _MessageT | None: ... + def pop(self, key: str, default: None = None) -> _MessageT_co | None: ... @overload - def pop(self, key: str, default: _T) -> _MessageT | _T: ... - def popitem(self) -> tuple[str, _MessageT]: ... - def update(self, arg: _HasIteritems | _HasItems | Iterable[tuple[str, _MessageData]] | None = None) -> None: ... + def pop(self, key: str, default: _T) -> _MessageT_co | _T: ... + + def popitem(self) -> tuple[str, _MessageT_co]: ... + def update( + self, arg: _HasIteritems | SupportsItems[str, _MessageData] | Iterable[tuple[str, _MessageData]] | None = None + ) -> None: ... @abstractmethod def flush(self) -> None: ... @abstractmethod @@ -101,19 +129,21 @@ class Mailbox(Generic[_MessageT]): def unlock(self) -> None: ... @abstractmethod def close(self) -> None: ... + # Undocumented, called by subclasses to parse added messages. + def _dump_message(self, message: _MessageData, target: SupportsWrite[bytes], mangle_from_: bool = False) -> None: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class Maildir(Mailbox[MaildirMessage]): colon: str def __init__( - self, dirname: StrPath, factory: Callable[[IO[Any]], MaildirMessage] | None = None, create: bool = True + self, dirname: StrPath, factory: Callable[[_GetFileReturn], MaildirMessage] | None = None, create: bool = True ) -> None: ... - def add(self, message: _MessageData) -> str: ... + def add(self, message: _MessageData | MaildirMessage) -> str: ... def remove(self, key: str) -> None: ... - def __setitem__(self, key: str, message: _MessageData) -> None: ... + def __setitem__(self, key: str, message: _MessageData | MaildirMessage) -> None: ... def get_message(self, key: str) -> MaildirMessage: ... def get_bytes(self, key: str) -> bytes: ... - def get_file(self, key: str) -> _ProxyFile[bytes]: ... + def get_file(self, key: str) -> _ProxyFile: ... if sys.version_info >= (3, 13): def get_info(self, key: str) -> str: ... def set_info(self, key: str, info: str) -> None: ... @@ -136,7 +166,7 @@ class Maildir(Mailbox[MaildirMessage]): def clean(self) -> None: ... def next(self) -> str | None: ... -class _singlefileMailbox(Mailbox[_MessageT], metaclass=ABCMeta): +class _singlefileMailbox(Mailbox[_MessageT_co], metaclass=ABCMeta): def add(self, message: _MessageData) -> str: ... def remove(self, key: str) -> None: ... def __setitem__(self, key: str, message: _MessageData) -> None: ... @@ -148,26 +178,32 @@ class _singlefileMailbox(Mailbox[_MessageT], metaclass=ABCMeta): def flush(self) -> None: ... def close(self) -> None: ... -class _mboxMMDF(_singlefileMailbox[_MessageT]): - def get_message(self, key: str) -> _MessageT: ... - def get_file(self, key: str, from_: bool = False) -> _PartialFile[bytes]: ... +class _mboxMMDF(_singlefileMailbox[_MessageT_co]): + def get_message(self, key: str) -> _MessageT_co: ... + def get_file(self, key: str, from_: bool = False) -> _PartialFile: ... def get_bytes(self, key: str, from_: bool = False) -> bytes: ... def get_string(self, key: str, from_: bool = False) -> str: ... class mbox(_mboxMMDF[mboxMessage]): - def __init__(self, path: StrPath, factory: Callable[[IO[Any]], mboxMessage] | None = None, create: bool = True) -> None: ... + def __init__( + self, path: StrPath, factory: Callable[[_GetFileReturn], mboxMessage] | None = None, create: bool = True + ) -> None: ... class MMDF(_mboxMMDF[MMDFMessage]): - def __init__(self, path: StrPath, factory: Callable[[IO[Any]], MMDFMessage] | None = None, create: bool = True) -> None: ... + def __init__( + self, path: StrPath, factory: Callable[[_GetFileReturn], MMDFMessage] | None = None, create: bool = True + ) -> None: ... class MH(Mailbox[MHMessage]): - def __init__(self, path: StrPath, factory: Callable[[IO[Any]], MHMessage] | None = None, create: bool = True) -> None: ... + def __init__( + self, path: StrPath, factory: Callable[[_GetFileReturn], MHMessage] | None = None, create: bool = True + ) -> None: ... def add(self, message: _MessageData) -> str: ... def remove(self, key: str) -> None: ... def __setitem__(self, key: str, message: _MessageData) -> None: ... def get_message(self, key: str) -> MHMessage: ... def get_bytes(self, key: str) -> bytes: ... - def get_file(self, key: str) -> _ProxyFile[bytes]: ... + def get_file(self, key: str) -> _ProxyFile: ... def iterkeys(self) -> Iterator[str]: ... def __contains__(self, key: str) -> bool: ... def __len__(self) -> int: ... @@ -184,13 +220,15 @@ class MH(Mailbox[MHMessage]): def pack(self) -> None: ... class Babyl(_singlefileMailbox[BabylMessage]): - def __init__(self, path: StrPath, factory: Callable[[IO[Any]], BabylMessage] | None = None, create: bool = True) -> None: ... + def __init__( + self, path: StrPath, factory: Callable[[_GetFileReturn], BabylMessage] | None = None, create: bool = True + ) -> None: ... def get_message(self, key: str) -> BabylMessage: ... def get_bytes(self, key: str) -> bytes: ... - def get_file(self, key: str) -> IO[bytes]: ... + def get_file(self, key: str) -> io.BytesIO: ... def get_labels(self) -> list[str]: ... -class Message(email.message.Message): +class Message(email.message.Message[str, str]): def __init__(self, message: _MessageData | None = None) -> None: ... class MaildirMessage(Message): @@ -232,18 +270,19 @@ class BabylMessage(Message): class MMDFMessage(_mboxMMDFMessage): ... -class _ProxyFile(Generic[AnyStr]): - def __init__(self, f: IO[AnyStr], pos: int | None = None) -> None: ... - def read(self, size: int | None = None) -> AnyStr: ... - def read1(self, size: int | None = None) -> AnyStr: ... - def readline(self, size: int | None = None) -> AnyStr: ... - def readlines(self, sizehint: int | None = None) -> list[AnyStr]: ... - def __iter__(self) -> Iterator[AnyStr]: ... +# Until Python 3.14, this class was technically - but unnecessarily - generic at runtime. +class _ProxyFile: + def __init__(self, f: _GetFileReturn, pos: int | None = None) -> None: ... + def read(self, size: int | None = None) -> bytes: ... + def read1(self, size: int | None = None) -> bytes: ... + def readline(self, size: int | None = None) -> bytes: ... + def readlines(self, sizehint: int | None = None) -> list[bytes]: ... + def __iter__(self) -> Iterator[bytes]: ... def tell(self) -> int: ... def seek(self, offset: int, whence: int = 0) -> None: ... def close(self) -> None: ... def __enter__(self) -> Self: ... - def __exit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ... + def __exit__(self, *exc: Unused) -> None: ... def readable(self) -> bool: ... def writable(self) -> bool: ... def seekable(self) -> bool: ... @@ -252,8 +291,8 @@ class _ProxyFile(Generic[AnyStr]): def closed(self) -> bool: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... -class _PartialFile(_ProxyFile[AnyStr]): - def __init__(self, f: IO[AnyStr], start: int | None = None, stop: int | None = None) -> None: ... +class _PartialFile(_ProxyFile): + def __init__(self, f: _GetFileReturn, start: int | None = None, stop: int | None = None) -> None: ... class Error(Exception): ... class NoSuchMailboxError(Error): ... diff --git a/mypy/typeshed/stdlib/mailcap.pyi b/mypy/typeshed/stdlib/mailcap.pyi index ce549e01f528c..74c32694a7f9d 100644 --- a/mypy/typeshed/stdlib/mailcap.pyi +++ b/mypy/typeshed/stdlib/mailcap.pyi @@ -1,5 +1,5 @@ from collections.abc import Mapping, Sequence -from typing_extensions import TypeAlias +from typing import TypeAlias _Cap: TypeAlias = dict[str, str | int] diff --git a/mypy/typeshed/stdlib/marshal.pyi b/mypy/typeshed/stdlib/marshal.pyi index 46c421e4ce307..d72abe7758b76 100644 --- a/mypy/typeshed/stdlib/marshal.pyi +++ b/mypy/typeshed/stdlib/marshal.pyi @@ -2,8 +2,7 @@ import builtins import sys import types from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite -from typing import Any, Final -from typing_extensions import TypeAlias +from typing import Any, Final, TypeAlias version: Final[int] @@ -28,7 +27,11 @@ _Marshallable: TypeAlias = ( | ReadableBuffer ) -if sys.version_info >= (3, 14): +if sys.version_info >= (3, 15): + def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 6, /, *, allow_code: bool = True) -> None: ... + def dumps(value: _Marshallable, version: int = 6, /, *, allow_code: bool = True) -> bytes: ... + +elif sys.version_info >= (3, 14): def dump(value: _Marshallable, file: SupportsWrite[bytes], version: int = 5, /, *, allow_code: bool = True) -> None: ... def dumps(value: _Marshallable, version: int = 5, /, *, allow_code: bool = True) -> bytes: ... diff --git a/mypy/typeshed/stdlib/math.pyi b/mypy/typeshed/stdlib/math/__init__.pyi similarity index 92% rename from mypy/typeshed/stdlib/math.pyi rename to mypy/typeshed/stdlib/math/__init__.pyi index 1903d488f7bb3..4839bf7e90df1 100644 --- a/mypy/typeshed/stdlib/math.pyi +++ b/mypy/typeshed/stdlib/math/__init__.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import SupportsMul, SupportsRMul from collections.abc import Iterable -from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, TypeAlias, TypeVar, overload, type_check_only _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) @@ -34,6 +33,7 @@ class _SupportsCeil(Protocol[_T_co]): def ceil(x: _SupportsCeil[_T], /) -> _T: ... @overload def ceil(x: _SupportsFloatOrIndex, /) -> int: ... + def comb(n: SupportsIndex, k: SupportsIndex, /) -> int: ... def copysign(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... def cos(x: _SupportsFloatOrIndex, /) -> float: ... @@ -50,6 +50,7 @@ if sys.version_info >= (3, 11): def expm1(x: _SupportsFloatOrIndex, /) -> float: ... def fabs(x: _SupportsFloatOrIndex, /) -> float: ... def factorial(x: SupportsIndex, /) -> int: ... + @type_check_only class _SupportsFloor(Protocol[_T_co]): def __floor__(self) -> _T_co: ... @@ -58,7 +59,13 @@ class _SupportsFloor(Protocol[_T_co]): def floor(x: _SupportsFloor[_T], /) -> _T: ... @overload def floor(x: _SupportsFloatOrIndex, /) -> int: ... + def fmod(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... + +if sys.version_info >= (3, 15): + def fmax(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... + def fmin(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... + def frexp(x: _SupportsFloatOrIndex, /) -> tuple[float, int]: ... def fsum(seq: Iterable[_SupportsFloatOrIndex], /) -> float: ... def gamma(x: _SupportsFloatOrIndex, /) -> float: ... @@ -74,6 +81,11 @@ def isclose( def isinf(x: _SupportsFloatOrIndex, /) -> bool: ... def isfinite(x: _SupportsFloatOrIndex, /) -> bool: ... def isnan(x: _SupportsFloatOrIndex, /) -> bool: ... + +if sys.version_info >= (3, 15): + def isnormal(x: _SupportsFloatOrIndex, /) -> bool: ... + def issubnormal(x: _SupportsFloatOrIndex, /) -> bool: ... + def isqrt(n: SupportsIndex, /) -> int: ... def lcm(*integers: SupportsIndex) -> int: ... def ldexp(x: _SupportsFloatOrIndex, i: int, /) -> float: ... @@ -116,9 +128,14 @@ def prod(iterable: Iterable[bool | _LiteralInteger], /, *, start: int = 1) -> in def prod(iterable: Iterable[_SupportsProdNoDefaultT], /) -> _SupportsProdNoDefaultT | Literal[1]: ... @overload def prod(iterable: Iterable[_MultiplicableT1], /, *, start: _MultiplicableT2) -> _MultiplicableT1 | _MultiplicableT2: ... + def radians(x: _SupportsFloatOrIndex, /) -> float: ... def remainder(x: _SupportsFloatOrIndex, y: _SupportsFloatOrIndex, /) -> float: ... def sin(x: _SupportsFloatOrIndex, /) -> float: ... + +if sys.version_info >= (3, 15): + def signbit(x: _SupportsFloatOrIndex, /) -> bool: ... + def sinh(x: _SupportsFloatOrIndex, /) -> float: ... if sys.version_info >= (3, 12): diff --git a/mypy/typeshed/stdlib/math/integer.pyi b/mypy/typeshed/stdlib/math/integer.pyi new file mode 100644 index 0000000000000..6d6d6b3e82dce --- /dev/null +++ b/mypy/typeshed/stdlib/math/integer.pyi @@ -0,0 +1,8 @@ +from typing import SupportsIndex + +def comb(n: SupportsIndex, k: SupportsIndex, /) -> int: ... +def factorial(n: SupportsIndex, /) -> int: ... +def gcd(*integers: SupportsIndex) -> int: ... +def isqrt(n: SupportsIndex, /) -> int: ... +def lcm(*integers: SupportsIndex) -> int: ... +def perm(n: SupportsIndex, k: SupportsIndex | None = None, /) -> int: ... diff --git a/mypy/typeshed/stdlib/mmap.pyi b/mypy/typeshed/stdlib/mmap.pyi index 12425f703aa1a..587b91d86c084 100644 --- a/mypy/typeshed/stdlib/mmap.pyi +++ b/mypy/typeshed/stdlib/mmap.pyi @@ -15,8 +15,7 @@ ALLOCATIONGRANULARITY: Final[int] if sys.platform == "linux": MAP_DENYWRITE: Final[int] MAP_EXECUTABLE: Final[int] - if sys.version_info >= (3, 10): - MAP_POPULATE: Final[int] + MAP_POPULATE: Final[int] if sys.version_info >= (3, 11) and sys.platform != "win32" and sys.platform != "darwin": MAP_STACK: Final[int] @@ -28,13 +27,29 @@ if sys.platform != "win32": PROT_EXEC: Final[int] PROT_READ: Final[int] PROT_WRITE: Final[int] + if sys.version_info >= (3, 15): + MS_ASYNC: Final[int] + MS_INVALIDATE: Final[int] + MS_SYNC: Final[int] PAGESIZE: Final[int] @disjoint_base class mmap: if sys.platform == "win32": - def __new__(cls, fileno: int, length: int, tagname: str | None = None, access: int = 0, offset: int = 0) -> Self: ... + if sys.version_info >= (3, 15): + def __new__( + cls, + fileno: int, + length: int, + tagname: str | None = None, + access: int = 0, + offset: int = 0, + *, + trackfd: bool = True, + ) -> Self: ... + else: + def __new__(cls, fileno: int, length: int, tagname: str | None = None, access: int = 0, offset: int = 0) -> Self: ... else: if sys.version_info >= (3, 13): def __new__( @@ -54,11 +69,16 @@ class mmap: ) -> Self: ... def close(self) -> None: ... - def flush(self, offset: int = 0, size: int = ..., /) -> None: ... + if sys.version_info >= (3, 15): + def flush(self, offset: int = 0, size: int = ..., /, *, flags: int = 0) -> None: ... + else: + def flush(self, offset: int = 0, size: int = ..., /) -> None: ... + def move(self, dest: int, src: int, count: int, /) -> None: ... def read_byte(self) -> int: ... def readline(self) -> bytes: ... - def resize(self, newsize: int, /) -> None: ... + if sys.version_info < (3, 15) or sys.platform != "darwin": + def resize(self, newsize: int, /) -> None: ... if sys.platform != "win32": def seek(self, pos: int, whence: Literal[0, 1, 2, 3, 4] = os.SEEK_SET, /) -> None: ... else: @@ -70,21 +90,36 @@ class mmap: def __len__(self) -> int: ... closed: bool if sys.platform != "win32": - def madvise(self, option: int, start: int = 0, length: int = ..., /) -> None: ... + if sys.version_info >= (3, 15): + def madvise(self, option: int, start: int = 0, length: int | None = None, /) -> None: ... + else: + def madvise(self, option: int, start: int = 0, length: int = ..., /) -> None: ... + + if sys.version_info >= (3, 15): + def find(self, view: ReadableBuffer, start: int | None = None, end: int | None = None, /) -> int: ... + def rfind(self, view: ReadableBuffer, start: int | None = None, end: int | None = None, /) -> int: ... + + else: + def find(self, view: ReadableBuffer, start: int = ..., end: int = ..., /) -> int: ... + def rfind(self, view: ReadableBuffer, start: int = ..., end: int = ..., /) -> int: ... - def find(self, view: ReadableBuffer, start: int = ..., end: int = ..., /) -> int: ... - def rfind(self, view: ReadableBuffer, start: int = ..., end: int = ..., /) -> int: ... def read(self, n: int | None = None, /) -> bytes: ... def write(self, bytes: ReadableBuffer, /) -> int: ... + if sys.version_info >= (3, 15): + def set_name(self, name: str, /) -> None: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> int: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> bytes: ... + def __delitem__(self, key: SupportsIndex | slice[SupportsIndex | None], /) -> NoReturn: ... + @overload def __setitem__(self, key: SupportsIndex, value: int, /) -> None: ... @overload def __setitem__(self, key: slice[SupportsIndex | None], value: ReadableBuffer, /) -> None: ... + # Doesn't actually exist, but the object actually supports "in" because it has __getitem__, # so we claim that there is also a __contains__ to help type checkers. def __contains__(self, o: object, /) -> bool: ... @@ -129,7 +164,7 @@ if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win MADV_CORE: Final[int] MADV_PROTECT: Final[int] -if sys.version_info >= (3, 10) and sys.platform == "darwin": +if sys.platform == "darwin": MADV_FREE_REUSABLE: Final[int] MADV_FREE_REUSE: Final[int] diff --git a/mypy/typeshed/stdlib/msilib/__init__.pyi b/mypy/typeshed/stdlib/msilib/__init__.pyi index 622f585f5beea..565a52d53499b 100644 --- a/mypy/typeshed/stdlib/msilib/__init__.pyi +++ b/mypy/typeshed/stdlib/msilib/__init__.pyi @@ -1,5 +1,6 @@ import sys -from collections.abc import Container, Iterable, Sequence +from _typeshed import MaybeNone +from collections.abc import Container, Iterable from types import ModuleType from typing import Any, Final @@ -7,6 +8,8 @@ if sys.platform == "win32": from _msi import * from _msi import _Database + from .sequence import _SequenceType + AMD64: Final[bool] Win64: Final[bool] @@ -33,10 +36,7 @@ if sys.platform == "win32": class _Unspecified: ... def change_sequence( - seq: Sequence[tuple[str, str | None, int]], - action: str, - seqno: int | type[_Unspecified] = ..., - cond: str | type[_Unspecified] = ..., + seq: _SequenceType, action: str, seqno: int | type[_Unspecified] = ..., cond: str | type[_Unspecified] = ... ) -> None: ... def add_data(db: _Database, table: str, values: Iterable[tuple[Any, ...]]) -> None: ... def add_stream(db: _Database, name: str, path: str) -> None: ... @@ -54,7 +54,7 @@ if sys.platform == "win32": index: int def __init__(self, name: str) -> None: ... def gen_id(self, file: str) -> str: ... - def append(self, full: str, file: str, logical: str) -> tuple[int, str]: ... + def append(self, full: str, file: str, logical: str | None) -> tuple[int, str] | MaybeNone: ... def commit(self, db: _Database) -> None: ... _directories: set[str] @@ -62,7 +62,7 @@ if sys.platform == "win32": class Directory: db: _Database cab: CAB - basedir: str + basedir: Directory | None physical: str logical: str component: str | None @@ -75,7 +75,7 @@ if sys.platform == "win32": self, db: _Database, cab: CAB, - basedir: str, + basedir: Directory | None, physical: str, _logical: str, default: str, @@ -146,8 +146,8 @@ if sys.platform == "win32": attr: int, title: str, first: str, - default: str, - cancel: str, + default: str | None, + cancel: str | None, ) -> None: ... def control( self, diff --git a/mypy/typeshed/stdlib/msilib/sequence.pyi b/mypy/typeshed/stdlib/msilib/sequence.pyi index a9f5c24717bd3..9b01c416f1d68 100644 --- a/mypy/typeshed/stdlib/msilib/sequence.pyi +++ b/mypy/typeshed/stdlib/msilib/sequence.pyi @@ -1,6 +1,5 @@ import sys -from typing import Final -from typing_extensions import TypeAlias +from typing import Final, TypeAlias if sys.platform == "win32": _SequenceType: TypeAlias = list[tuple[str, str | None, int]] diff --git a/mypy/typeshed/stdlib/msvcrt.pyi b/mypy/typeshed/stdlib/msvcrt.pyi index 5feca8eab5c1c..1518f7974de7c 100644 --- a/mypy/typeshed/stdlib/msvcrt.pyi +++ b/mypy/typeshed/stdlib/msvcrt.pyi @@ -28,5 +28,4 @@ if sys.platform == "win32": def ungetwch(unicode_char: str, /) -> None: ... def heapmin() -> None: ... def SetErrorMode(mode: int, /) -> int: ... - if sys.version_info >= (3, 10): - def GetErrorMode() -> int: ... # undocumented + def GetErrorMode() -> int: ... # undocumented diff --git a/mypy/typeshed/stdlib/multiprocessing/connection.pyi b/mypy/typeshed/stdlib/multiprocessing/connection.pyi index cd4fa102c0f3e..e8366e9a8ba58 100644 --- a/mypy/typeshed/stdlib/multiprocessing/connection.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/connection.pyi @@ -3,8 +3,8 @@ import sys from _typeshed import Incomplete, ReadableBuffer from collections.abc import Iterable from types import TracebackType -from typing import Any, Generic, SupportsIndex, TypeVar -from typing_extensions import Self, TypeAlias +from typing import Any, Generic, SupportsIndex, TypeAlias, TypeVar +from typing_extensions import Self __all__ = ["Client", "Listener", "Pipe", "wait"] @@ -46,7 +46,11 @@ class Listener: def __init__( self, address: _Address | None = None, family: str | None = None, backlog: int = 1, authkey: bytes | None = None ) -> None: ... - def accept(self) -> Connection[Incomplete, Incomplete]: ... + if sys.platform != "win32": + def accept(self) -> Connection[Incomplete, Incomplete]: ... + else: + def accept(self) -> Connection[Incomplete, Incomplete] | PipeConnection[Incomplete, Incomplete]: ... + def close(self) -> None: ... @property def address(self) -> _Address: ... @@ -59,16 +63,23 @@ class Listener: # Any: send and recv methods unused if sys.version_info >= (3, 12): - def deliver_challenge(connection: Connection[Any, Any], authkey: bytes, digest_name: str = "sha256") -> None: ... + def deliver_challenge(connection: _ConnectionBase[Any, Any], authkey: bytes, digest_name: str = "sha256") -> None: ... else: - def deliver_challenge(connection: Connection[Any, Any], authkey: bytes) -> None: ... + def deliver_challenge(connection: _ConnectionBase[Any, Any], authkey: bytes) -> None: ... -def answer_challenge(connection: Connection[Any, Any], authkey: bytes) -> None: ... +def answer_challenge(connection: _ConnectionBase[Any, Any], authkey: bytes) -> None: ... def wait( - object_list: Iterable[Connection[_SendT_contra, _RecvT_co] | socket.socket | int], timeout: float | None = None -) -> list[Connection[_SendT_contra, _RecvT_co] | socket.socket | int]: ... -def Client(address: _Address, family: str | None = None, authkey: bytes | None = None) -> Connection[Any, Any]: ... + object_list: Iterable[_ConnectionBase[_SendT_contra, _RecvT_co] | socket.socket | int], timeout: float | None = None +) -> list[_ConnectionBase[_SendT_contra, _RecvT_co] | socket.socket | int]: ... + +if sys.platform != "win32": + def Client(address: _Address, family: str | None = None, authkey: bytes | None = None) -> Connection[Any, Any]: ... + +else: + def Client( + address: _Address, family: str | None = None, authkey: bytes | None = None + ) -> Connection[Any, Any] | PipeConnection[Any, Any]: ... # N.B. Keep this in sync with multiprocessing.context.BaseContext.Pipe. # _ConnectionBase is the common base class of Connection and PipeConnection diff --git a/mypy/typeshed/stdlib/multiprocessing/context.pyi b/mypy/typeshed/stdlib/multiprocessing/context.pyi index 03d1d2e5c2203..13fd967515d09 100644 --- a/mypy/typeshed/stdlib/multiprocessing/context.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/context.pyi @@ -9,8 +9,7 @@ from multiprocessing.managers import SyncManager from multiprocessing.pool import Pool as _Pool from multiprocessing.process import BaseProcess from multiprocessing.sharedctypes import Synchronized, SynchronizedArray, SynchronizedString -from typing import Any, ClassVar, Literal, TypeVar, overload -from typing_extensions import TypeAlias +from typing import Any, ClassVar, Literal, TypeAlias, TypeVar, overload if sys.platform != "win32": from multiprocessing.connection import Connection @@ -75,14 +74,17 @@ class BaseContext: initargs: Iterable[Any] = (), maxtasksperchild: int | None = None, ) -> _Pool: ... + @overload def RawValue(self, typecode_or_type: type[_CT], *args: Any) -> _CT: ... @overload def RawValue(self, typecode_or_type: str, *args: Any) -> Any: ... + @overload def RawArray(self, typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any]) -> ctypes.Array[_CT]: ... @overload def RawArray(self, typecode_or_type: str, size_or_initializer: int | Sequence[Any]) -> Any: ... + @overload def Value( self, typecode_or_type: type[_SimpleCData[_T]], *args: Any, lock: Literal[True] | _LockLike = True @@ -95,6 +97,7 @@ class BaseContext: def Value(self, typecode_or_type: str, *args: Any, lock: Literal[True] | _LockLike = True) -> Synchronized[Any]: ... @overload def Value(self, typecode_or_type: str | type[_CData], *args: Any, lock: bool | _LockLike = True) -> Any: ... + @overload def Array( self, typecode_or_type: type[_SimpleCData[_T]], size_or_initializer: int | Sequence[Any], *, lock: Literal[False] @@ -119,40 +122,44 @@ class BaseContext: def Array( self, typecode_or_type: str | type[_CData], size_or_initializer: int | Sequence[Any], *, lock: bool | _LockLike = True ) -> Any: ... + def freeze_support(self) -> None: ... def get_logger(self) -> Logger: ... def log_to_stderr(self, level: _LoggingLevel | None = None) -> Logger: ... def allow_connection_pickling(self) -> None: ... def set_executable(self, executable: str) -> None: ... - def set_forkserver_preload(self, module_names: list[str]) -> None: ... + if sys.version_info >= (3, 15): + def set_forkserver_preload( + self, module_names: list[str], *, on_error: Literal["ignore", "warn", "fail"] = "ignore" + ) -> None: ... + else: + def set_forkserver_preload(self, module_names: list[str]) -> None: ... + + @overload + def get_context(self, method: None = None) -> DefaultContext: ... + @overload + def get_context(self, method: Literal["spawn"]) -> SpawnContext: ... if sys.platform != "win32": - @overload - def get_context(self, method: None = None) -> DefaultContext: ... - @overload - def get_context(self, method: Literal["spawn"]) -> SpawnContext: ... @overload def get_context(self, method: Literal["fork"]) -> ForkContext: ... @overload def get_context(self, method: Literal["forkserver"]) -> ForkServerContext: ... - @overload - def get_context(self, method: str) -> BaseContext: ... - else: - @overload - def get_context(self, method: None = None) -> DefaultContext: ... - @overload - def get_context(self, method: Literal["spawn"]) -> SpawnContext: ... - @overload - def get_context(self, method: str) -> BaseContext: ... + + @overload + def get_context(self, method: str) -> BaseContext: ... @overload def get_start_method(self, allow_none: Literal[False] = False) -> str: ... @overload def get_start_method(self, allow_none: bool) -> str | None: ... + def set_start_method(self, method: str | None, force: bool = False) -> None: ... + @property def reducer(self) -> str: ... @reducer.setter def reducer(self, reduction: str) -> None: ... + def _check_available(self) -> None: ... class Process(BaseProcess): @@ -201,6 +208,13 @@ if sys.platform != "win32": Process: ClassVar[type[ForkServerProcess]] def _force_start_method(method: str) -> None: ... -def get_spawning_popen() -> Any | None: ... -def set_spawning_popen(popen: Any) -> None: ... + +if sys.platform != "win32": + def get_spawning_popen() -> popen_forkserver.Popen | popen_spawn_posix.Popen | None: ... + def set_spawning_popen(popen: popen_forkserver.Popen | popen_spawn_posix.Popen | None) -> None: ... + +else: + def get_spawning_popen() -> popen_spawn_win32.Popen | None: ... + def set_spawning_popen(popen: popen_spawn_win32.Popen | None) -> None: ... + def assert_spawning(obj: Any) -> None: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi b/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi index 570b492e9daf3..e48be5f07949e 100644 --- a/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/forkserver.pyi @@ -2,7 +2,7 @@ import sys from _typeshed import FileDescriptorLike, Unused from collections.abc import Sequence from struct import Struct -from typing import Any, Final +from typing import Any, Final, Literal __all__ = ["ensure_running", "get_inherited_fds", "connect_to_new_process", "set_forkserver_preload"] @@ -10,12 +10,31 @@ MAXFDS_TO_SEND: Final = 256 SIGNED_STRUCT: Final[Struct] class ForkServer: - def set_forkserver_preload(self, modules_names: list[str]) -> None: ... + if sys.version_info >= (3, 15): + def set_forkserver_preload( + self, modules_names: list[str], *, on_error: Literal["ignore", "warn", "fail"] = "ignore" + ) -> None: ... + else: + def set_forkserver_preload(self, modules_names: list[str]) -> None: ... + def get_inherited_fds(self) -> list[int] | None: ... def connect_to_new_process(self, fds: Sequence[int]) -> tuple[int, int]: ... def ensure_running(self) -> None: ... -if sys.version_info >= (3, 14): +if sys.version_info >= (3, 15): + def main( + listener_fd: int | None, + alive_r: FileDescriptorLike, + preload: Sequence[str], + main_path: str | None = None, + sys_path: list[str] | None = None, + *, + sys_argv: list[str] | None = None, + authkey_r: int | None = None, + on_error: str = "ignore", + ) -> None: ... + +elif sys.version_info >= (3, 14): # `sys_argv` parameter added in Python 3.14.3 def main( listener_fd: int | None, diff --git a/mypy/typeshed/stdlib/multiprocessing/heap.pyi b/mypy/typeshed/stdlib/multiprocessing/heap.pyi index 38191a099f1ec..bf6f853a97608 100644 --- a/mypy/typeshed/stdlib/multiprocessing/heap.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/heap.pyi @@ -1,9 +1,8 @@ import sys -from _typeshed import Incomplete from collections.abc import Callable from mmap import mmap -from typing import Protocol, type_check_only -from typing_extensions import TypeAlias +from multiprocessing import popen_forkserver, popen_spawn_posix, resource_sharer +from typing import Protocol, TypeAlias, type_check_only __all__ = ["BufferWrapper"] @@ -24,7 +23,12 @@ if sys.platform != "win32": class _SupportsDetach(Protocol): def detach(self) -> int: ... - def reduce_arena(a: Arena) -> tuple[Callable[[int, _SupportsDetach], Arena], tuple[int, Incomplete]]: ... + def reduce_arena( + a: Arena, + ) -> tuple[ + Callable[[int, _SupportsDetach], Arena], + tuple[int, popen_forkserver._DupFd | popen_spawn_posix._DupFd | resource_sharer.DupFd], + ]: ... def rebuild_arena(size: int, dupfd: _SupportsDetach) -> Arena: ... class Heap: diff --git a/mypy/typeshed/stdlib/multiprocessing/managers.pyi b/mypy/typeshed/stdlib/multiprocessing/managers.pyi index bb169ed6b2ed4..40639e8678348 100644 --- a/mypy/typeshed/stdlib/multiprocessing/managers.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/managers.pyi @@ -14,8 +14,8 @@ from collections.abc import ( Set as AbstractSet, ) from types import GenericAlias, TracebackType -from typing import Any, AnyStr, ClassVar, Generic, SupportsIndex, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing import Any, AnyStr, ClassVar, Generic, SupportsIndex, TypeAlias, TypeVar, overload +from typing_extensions import Self from . import pool from .connection import Connection, _Address @@ -81,18 +81,21 @@ if sys.version_info >= (3, 13): def __delitem__(self, key: _KT, /) -> None: ... def __iter__(self) -> Iterator[_KT]: ... def copy(self) -> dict[_KT, _VT]: ... + @overload # type: ignore[override] def get(self, key: _KT, /) -> _VT | None: ... @overload def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + @overload def pop(self, key: _KT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _VT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def keys(self) -> list[_KT]: ... # type: ignore[override] def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override] def values(self) -> list[_VT]: ... # type: ignore[override] @@ -102,15 +105,19 @@ if sys.version_info >= (3, 13): def fromkeys(self, iterable: Iterable[_T], value: None = None, /) -> dict[_T, Any | None]: ... @overload def fromkeys(self, iterable: Iterable[_T], value: _S, /) -> dict[_T, _S]: ... + def __reversed__(self) -> Iterator[_KT]: ... + @overload def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... @overload def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... @overload def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload # type: ignore[misc] def __ior__(self, value: SupportsKeysAndGetItem[_KT, _VT], /) -> Self: ... @overload @@ -128,18 +135,21 @@ else: def __delitem__(self, key: _KT, /) -> None: ... def __iter__(self) -> Iterator[_KT]: ... def copy(self) -> dict[_KT, _VT]: ... + @overload # type: ignore[override] def get(self, key: _KT, /) -> _VT | None: ... @overload def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + @overload def pop(self, key: _KT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _VT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def keys(self) -> list[_KT]: ... # type: ignore[override] def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override] def values(self) -> list[_VT]: ... # type: ignore[override] @@ -194,14 +204,17 @@ class BaseListProxy(BaseProxy, MutableSequence[_T]): def __len__(self) -> int: ... def __add__(self, x: list[_T], /) -> list[_T]: ... def __delitem__(self, i: SupportsIndex | slice[SupportsIndex | None], /) -> None: ... + @overload def __getitem__(self, i: SupportsIndex, /) -> _T: ... @overload def __getitem__(self, s: slice[SupportsIndex | None], /) -> list[_T]: ... + @overload def __setitem__(self, i: SupportsIndex, o: _T, /) -> None: ... @overload def __setitem__(self, s: slice[SupportsIndex | None], o: Iterable[_T], /) -> None: ... + def __mul__(self, n: SupportsIndex, /) -> list[_T]: ... def __rmul__(self, n: SupportsIndex, /) -> list[_T]: ... def __imul__(self, value: SupportsIndex, /) -> Self: ... @@ -217,6 +230,7 @@ class BaseListProxy(BaseProxy, MutableSequence[_T]): # Next methods are copied from builtins.list def clear(self) -> None: ... def copy(self) -> list[_T]: ... + # Use BaseListProxy[SupportsRichComparisonT] for the first overload rather than [SupportsRichComparison] # to work around invariance @overload @@ -250,11 +264,7 @@ class Server: ) -> None: ... def serve_forever(self) -> None: ... def accepter(self) -> None: ... - if sys.version_info >= (3, 10): - def handle_request(self, conn: _ServerConnection) -> None: ... - else: - def handle_request(self, c: _ServerConnection) -> None: ... - + def handle_request(self, conn: _ServerConnection) -> None: ... def serve_client(self, conn: _ServerConnection) -> None: ... def fallback_getvalue(self, conn: _ServerConnection, ident: str, obj: _T) -> _T: ... def fallback_str(self, conn: _ServerConnection, ident: str, obj: Any) -> str: ... @@ -334,6 +344,7 @@ class SyncManager(BaseManager): def Semaphore(self, value: int = 1) -> threading.Semaphore: ... def Array(self, typecode: Any, sequence: Sequence[_T]) -> Sequence[_T]: ... def Value(self, typecode: Any, value: _T) -> ValueProxy[_T]: ... + # Overloads are copied from builtins.dict.__init__ @overload def dict(self) -> DictProxy[Any, Any]: ... @@ -351,11 +362,13 @@ class SyncManager(BaseManager): def dict(self, iterable: Iterable[list[str]], /) -> DictProxy[str, str]: ... @overload def dict(self, iterable: Iterable[list[bytes]], /) -> DictProxy[bytes, bytes]: ... + # Overloads are copied from builtins.list.__init__ @overload def list(self, iterable: Iterable[_T], /) -> ListProxy[_T]: ... @overload def list(self) -> ListProxy[Any]: ... + if sys.version_info >= (3, 14): @overload def set(self, iterable: Iterable[_T], /) -> SetProxy[_T]: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/reduction.pyi b/mypy/typeshed/stdlib/multiprocessing/reduction.pyi index ddc676efccc39..476cf9b26f718 100644 --- a/mypy/typeshed/stdlib/multiprocessing/reduction.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/reduction.pyi @@ -6,7 +6,7 @@ from abc import ABCMeta from builtins import type as Type # alias to avoid name clash from collections.abc import Callable from copyreg import _DispatchTableType -from multiprocessing import connection +from multiprocessing import connection, popen_forkserver, popen_spawn_posix, resource_sharer from socket import socket from typing import Any, Final @@ -57,7 +57,7 @@ else: def send_handle(conn: HasFileno, handle: int, destination_pid: Unused) -> None: ... def recv_handle(conn: HasFileno) -> int: ... def sendfds(sock: socket, fds: list[int]) -> None: ... - def DupFd(fd: int) -> Any: ... # Return type is really hard to get right + def DupFd(fd: int) -> popen_forkserver._DupFd | popen_spawn_posix._DupFd | resource_sharer.DupFd: ... # These aliases are to work around pyright complaints. # Pyright doesn't like it when a class object is defined as an alias diff --git a/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi b/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi index f75a372a69a2d..90777a4e771bd 100644 --- a/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/shared_memory.pyi @@ -26,10 +26,12 @@ class SharedMemory: class ShareableList(Generic[_SLT]): shm: SharedMemory + @overload def __init__(self, sequence: None = None, *, name: str | None = None) -> None: ... @overload def __init__(self, sequence: Iterable[_SLT], *, name: str | None = None) -> None: ... + def __getitem__(self, position: int) -> _SLT: ... def __setitem__(self, position: int, value: _SLT) -> None: ... def __reduce__(self) -> tuple[Self, tuple[_SLT, ...]]: ... diff --git a/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi b/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi index f61ca26aab5d6..693fd2f701396 100644 --- a/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/sharedctypes.pyi @@ -16,10 +16,12 @@ _CT = TypeVar("_CT", bound=_CData) def RawValue(typecode_or_type: type[_CT], *args: Any) -> _CT: ... @overload def RawValue(typecode_or_type: str, *args: Any) -> Any: ... + @overload def RawArray(typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any]) -> ctypes.Array[_CT]: ... @overload def RawArray(typecode_or_type: str, size_or_initializer: int | Sequence[Any]) -> Any: ... + @overload def Value(typecode_or_type: type[_CT], *args: Any, lock: Literal[False], ctx: BaseContext | None = None) -> _CT: ... @overload @@ -34,6 +36,7 @@ def Value( def Value( typecode_or_type: str | type[_CData], *args: Any, lock: bool | _LockLike = True, ctx: BaseContext | None = None ) -> Any: ... + @overload def Array( typecode_or_type: type[_CT], size_or_initializer: int | Sequence[Any], *, lock: Literal[False], ctx: BaseContext | None = None @@ -70,7 +73,9 @@ def Array( lock: bool | _LockLike = True, ctx: BaseContext | None = None, ) -> Any: ... + def copy(obj: _CT) -> _CT: ... + @overload def synchronized(obj: _SimpleCData[_T], lock: _LockLike | None = None, ctx: Any | None = None) -> Synchronized[_T]: ... @overload @@ -81,6 +86,7 @@ def synchronized( ) -> SynchronizedArray[_T]: ... @overload def synchronized(obj: _CT, lock: _LockLike | None = None, ctx: Any | None = None) -> SynchronizedBase[_CT]: ... + @type_check_only class _AcquireFunc(Protocol): def __call__(self, block: bool = ..., timeout: float | None = ..., /) -> bool: ... @@ -102,14 +108,17 @@ class Synchronized(SynchronizedBase[_SimpleCData[_T]], Generic[_T]): class SynchronizedArray(SynchronizedBase[ctypes.Array[_SimpleCData[_T]]], Generic[_T]): def __len__(self) -> int: ... + @overload def __getitem__(self, i: slice[SupportsIndex | None]) -> list[_T]: ... @overload def __getitem__(self, i: SupportsIndex) -> _T: ... + @overload def __setitem__(self, i: slice[SupportsIndex | None], value: Iterable[_T]) -> None: ... @overload def __setitem__(self, i: SupportsIndex, value: _T) -> None: ... + def __getslice__(self, start: SupportsIndex, stop: SupportsIndex) -> list[_T]: ... def __setslice__(self, start: SupportsIndex, stop: SupportsIndex, values: Iterable[_T]) -> None: ... @@ -118,10 +127,12 @@ class SynchronizedString(SynchronizedArray[bytes]): def __getitem__(self, i: slice[SupportsIndex | None]) -> bytes: ... @overload def __getitem__(self, i: SupportsIndex) -> bytes: ... + @overload # type: ignore[override] def __setitem__(self, i: slice[SupportsIndex | None], value: bytes) -> None: ... @overload def __setitem__(self, i: SupportsIndex, value: bytes) -> None: ... + def __getslice__(self, start: SupportsIndex, stop: SupportsIndex) -> bytes: ... # type: ignore[override] def __setslice__(self, start: SupportsIndex, stop: SupportsIndex, values: bytes) -> None: ... # type: ignore[override] diff --git a/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi index 541e0b05dd8a6..889e71c061e11 100644 --- a/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/synchronize.pyi @@ -3,7 +3,7 @@ import threading from collections.abc import Callable from multiprocessing.context import BaseContext from types import TracebackType -from typing_extensions import TypeAlias +from typing import TypeAlias __all__ = ["Lock", "RLock", "Semaphore", "BoundedSemaphore", "Condition", "Event"] diff --git a/mypy/typeshed/stdlib/multiprocessing/util.pyi b/mypy/typeshed/stdlib/multiprocessing/util.pyi index 3583194c77e29..5eb31de77f0f7 100644 --- a/mypy/typeshed/stdlib/multiprocessing/util.pyi +++ b/mypy/typeshed/stdlib/multiprocessing/util.pyi @@ -82,6 +82,7 @@ class Finalize(Generic[_R_co]): kwargs: Mapping[str, Any] | None = None, exitpriority: int | None = None, ) -> None: ... + def __call__( self, wr: Unused = None, diff --git a/mypy/typeshed/stdlib/netrc.pyi b/mypy/typeshed/stdlib/netrc.pyi index 480f55a46d645..4b7035c3e565e 100644 --- a/mypy/typeshed/stdlib/netrc.pyi +++ b/mypy/typeshed/stdlib/netrc.pyi @@ -1,6 +1,6 @@ import sys from _typeshed import StrOrBytesPath -from typing_extensions import TypeAlias +from typing import TypeAlias __all__ = ["netrc", "NetrcParseError"] diff --git a/mypy/typeshed/stdlib/nntplib.pyi b/mypy/typeshed/stdlib/nntplib.pyi index 1fb1e79f69a1f..50b633ce1ad12 100644 --- a/mypy/typeshed/stdlib/nntplib.pyi +++ b/mypy/typeshed/stdlib/nntplib.pyi @@ -4,8 +4,8 @@ import ssl from _typeshed import Unused from builtins import list as _list # conflicts with a method named "list" from collections.abc import Iterable -from typing import IO, Any, Final, NamedTuple -from typing_extensions import Self, TypeAlias +from typing import IO, Any, Final, NamedTuple, TypeAlias +from typing_extensions import Self __all__ = [ "NNTP", diff --git a/mypy/typeshed/stdlib/ntpath.pyi b/mypy/typeshed/stdlib/ntpath.pyi index 074df075b9727..c912d77158ecc 100644 --- a/mypy/typeshed/stdlib/ntpath.pyi +++ b/mypy/typeshed/stdlib/ntpath.pyi @@ -51,6 +51,8 @@ if sys.version_info >= (3, 12): from posixpath import isjunction as isjunction, splitroot as splitroot if sys.version_info >= (3, 13): from genericpath import isdevdrive as isdevdrive +if sys.version_info >= (3, 15): + from genericpath import ALL_BUT_LAST as ALL_BUT_LAST __all__ = [ "normcase", @@ -97,6 +99,8 @@ if sys.version_info >= (3, 12): __all__ += ["isjunction", "splitroot"] if sys.version_info >= (3, 13): __all__ += ["isdevdrive", "isreserved"] +if sys.version_info >= (3, 15): + __all__ += ["ALL_BUT_LAST"] altsep: LiteralString @@ -110,14 +114,21 @@ def join(path: StrPath, /, *paths: StrPath) -> str: ... @overload def join(path: BytesPath, /, *paths: BytesPath) -> bytes: ... -if sys.platform == "win32": +if sys.version_info >= (3, 15): @overload - def realpath(path: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + def realpath(path: PathLike[AnyStr], /, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... @overload - def realpath(path: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + def realpath(path: AnyStr, /, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... else: - realpath = abspath + if sys.platform == "win32": + @overload + def realpath(path: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + @overload + def realpath(path: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + + else: + realpath = abspath if sys.version_info >= (3, 13): def isreserved(path: StrOrBytesPath) -> bool: ... diff --git a/mypy/typeshed/stdlib/nturl2path.pyi b/mypy/typeshed/stdlib/nturl2path.pyi index 014af8a0fd2ed..98686ed65420f 100644 --- a/mypy/typeshed/stdlib/nturl2path.pyi +++ b/mypy/typeshed/stdlib/nturl2path.pyi @@ -1,12 +1,6 @@ -import sys from typing_extensions import deprecated -if sys.version_info >= (3, 14): - @deprecated("The `nturl2path` module is deprecated since Python 3.14.") - def url2pathname(url: str) -> str: ... - @deprecated("The `nturl2path` module is deprecated since Python 3.14.") - def pathname2url(p: str) -> str: ... - -else: - def url2pathname(url: str) -> str: ... - def pathname2url(p: str) -> str: ... +@deprecated("The `nturl2path` module is deprecated since Python 3.14.") +def url2pathname(url: str) -> str: ... +@deprecated("The `nturl2path` module is deprecated since Python 3.14.") +def pathname2url(p: str) -> str: ... diff --git a/mypy/typeshed/stdlib/numbers.pyi b/mypy/typeshed/stdlib/numbers.pyi index 64fb16581e952..434e52c4cac80 100644 --- a/mypy/typeshed/stdlib/numbers.pyi +++ b/mypy/typeshed/stdlib/numbers.pyi @@ -120,12 +120,14 @@ class Real(Complex, _RealLike): def __floor__(self) -> _IntegralLike: ... @abstractmethod def __ceil__(self) -> _IntegralLike: ... + @abstractmethod @overload def __round__(self, ndigits: None = None) -> _IntegralLike: ... @abstractmethod @overload def __round__(self, ndigits: int) -> _RealLike: ... + def __divmod__(self, other) -> tuple[_RealLike, _RealLike]: ... def __rdivmod__(self, other) -> tuple[_RealLike, _RealLike]: ... @abstractmethod @@ -209,6 +211,7 @@ class Integral(Rational, _IntegralLike): def __neg__(self) -> _IntegralLike: ... @abstractmethod def __abs__(self) -> _IntegralLike: ... + @abstractmethod @overload def __round__(self, ndigits: None = None) -> _IntegralLike: ... diff --git a/mypy/typeshed/stdlib/opcode.pyi b/mypy/typeshed/stdlib/opcode.pyi index 67c2ef27ff360..3bc41db42bb63 100644 --- a/mypy/typeshed/stdlib/opcode.pyi +++ b/mypy/typeshed/stdlib/opcode.pyi @@ -1,6 +1,9 @@ import sys from typing import Final, Literal +if sys.version_info >= (3, 15): + from builtins import frozendict + __all__ = [ "cmp_op", "hasconst", @@ -40,7 +43,10 @@ if sys.version_info >= (3, 13): hasjump: Final[list[int]] opname: Final[list[str]] -opmap: Final[dict[str, int]] +if sys.version_info >= (3, 15): + opmap: Final[frozendict[str, int]] +else: + opmap: Final[dict[str, int]] HAVE_ARGUMENT: Final[int] EXTENDED_ARG: Final[int] diff --git a/mypy/typeshed/stdlib/operator.pyi b/mypy/typeshed/stdlib/operator.pyi index 2f919514b0b8b..5998d6d16e98b 100644 --- a/mypy/typeshed/stdlib/operator.pyi +++ b/mypy/typeshed/stdlib/operator.pyi @@ -192,6 +192,7 @@ class attrgetter(Generic[_T_co]): def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[Any, Any, Any, Any]]: ... @overload def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[Any, ...]]: ... + def __call__(self, obj: Any, /) -> _T_co: ... @final @@ -200,6 +201,7 @@ class itemgetter(Generic[_T_co]): def __new__(cls, item: _T, /) -> itemgetter[_T]: ... @overload def __new__(cls, item1: _T1, item2: _T2, /, *items: Unpack[_Ts]) -> itemgetter[tuple[_T1, _T2, Unpack[_Ts]]]: ... + # __key: _KT_contra in SupportsGetItem seems to be causing variance issues, ie: # TypeVar "_KT_contra@SupportsGetItem" is contravariant # "tuple[int, int]" is incompatible with protocol "SupportsIndex" diff --git a/mypy/typeshed/stdlib/optparse.pyi b/mypy/typeshed/stdlib/optparse.pyi index 305b6a4f06d66..9e5536610272e 100644 --- a/mypy/typeshed/stdlib/optparse.pyi +++ b/mypy/typeshed/stdlib/optparse.pyi @@ -184,6 +184,7 @@ class OptionContainer: def _check_conflict(self, option: Option) -> None: ... def _create_option_mappings(self) -> None: ... def _share_option_mappings(self, parser: OptionParser) -> None: ... + @overload def add_option(self, opt: Option, /) -> Option: ... @overload @@ -206,6 +207,7 @@ class OptionContainer: metavar: str | None = None, **kwargs: Any, # Allow arbitrary keyword arguments for user defined option_class ) -> Option: ... + def add_options(self, option_list: Iterable[Option]) -> None: ... def destroy(self) -> None: ... def format_option_help(self, formatter: HelpFormatter) -> str: ... @@ -280,10 +282,12 @@ class OptionParser(OptionContainer): def _process_args(self, largs: list[str], rargs: list[str], values: Values) -> None: ... def _process_long_opt(self, rargs: list[str], values: Values) -> None: ... def _process_short_opts(self, rargs: list[str], values: Values) -> None: ... + @overload def add_option_group(self, opt_group: OptionGroup, /) -> OptionGroup: ... @overload def add_option_group(self, title: str, /, description: str | None = None) -> OptionGroup: ... + def check_values(self, values: Values, args: list[str]) -> tuple[Values, list[str]]: ... def disable_interspersed_args(self) -> None: ... def enable_interspersed_args(self) -> None: ... diff --git a/mypy/typeshed/stdlib/os/__init__.pyi b/mypy/typeshed/stdlib/os/__init__.pyi index 66a9d1dd3bc6d..726e9702fb16b 100644 --- a/mypy/typeshed/stdlib/os/__init__.pyi +++ b/mypy/typeshed/stdlib/os/__init__.pyi @@ -35,13 +35,14 @@ from typing import ( Literal, NoReturn, Protocol, + TypeAlias, TypeVar, final, overload, runtime_checkable, type_check_only, ) -from typing_extensions import LiteralString, Self, TypeAlias, Unpack, deprecated +from typing_extensions import LiteralString, Self, Unpack, deprecated from . import path as _path @@ -176,9 +177,11 @@ __all__ = [ if sys.version_info >= (3, 14): # reload_environ was added to __all__ in Python 3.14.1 __all__ += ["readinto", "reload_environ"] +if sys.platform == "linux" and sys.version_info >= (3, 15): + __all__ += ["_clearenv"] if sys.platform == "darwin" and sys.version_info >= (3, 12): __all__ += ["PRIO_DARWIN_BG", "PRIO_DARWIN_NONUI", "PRIO_DARWIN_PROCESS", "PRIO_DARWIN_THREAD"] -if sys.platform == "darwin" and sys.version_info >= (3, 10): +if sys.platform == "darwin": __all__ += ["O_EVTONLY", "O_NOFOLLOW_ANY", "O_SYMLINK"] if sys.platform == "linux": __all__ += [ @@ -226,6 +229,31 @@ if sys.platform == "linux": ] if sys.platform == "linux" and sys.version_info >= (3, 14): __all__ += ["SCHED_DEADLINE", "SCHED_NORMAL"] +if sys.platform == "linux" and sys.version_info >= (3, 15): + __all__ += [ + "AT_NO_AUTOMOUNT", + "AT_STATX_DONT_SYNC", + "AT_STATX_FORCE_SYNC", + "AT_STATX_SYNC_AS_STAT", + "STATX_ATIME", + "STATX_BASIC_STATS", + "STATX_BLOCKS", + "STATX_BTIME", + "STATX_CTIME", + "STATX_DIOALIGN", + "STATX_GID", + "STATX_INO", + "STATX_MNT_ID", + "STATX_MNT_ID_UNIQUE", + "STATX_MODE", + "STATX_MTIME", + "STATX_NLINK", + "STATX_SIZE", + "STATX_TYPE", + "STATX_UID", + "statx", + "statx_result", + ] if sys.platform == "linux" and sys.version_info >= (3, 13): __all__ += [ "POSIX_SPAWN_CLOSEFROM", @@ -259,7 +287,7 @@ if sys.platform == "linux" and sys.version_info >= (3, 12): "unshare", "PIDFD_NONBLOCK", ] -if sys.platform == "linux" and sys.version_info >= (3, 10): +if sys.platform == "linux": __all__ += [ "EFD_CLOEXEC", "EFD_NONBLOCK", @@ -446,7 +474,9 @@ if sys.platform != "win32" and sys.version_info >= (3, 13): __all__ += ["grantpt", "posix_openpt", "ptsname", "unlockpt"] if sys.platform != "win32" and sys.version_info >= (3, 11): __all__ += ["login_tty"] -if sys.platform != "win32" and sys.version_info >= (3, 10): +if sys.platform != "win32" and sys.version_info >= (3, 15): + __all__ += ["NODEV", "O_FSYNC"] +elif sys.platform != "win32": __all__ += ["O_FSYNC"] if sys.platform != "darwin" and sys.platform != "win32": __all__ += [ @@ -661,12 +691,37 @@ if sys.platform != "linux" and sys.platform != "win32": O_SHLOCK: Final[int] O_EXLOCK: Final[int] -if sys.platform == "darwin" and sys.version_info >= (3, 10): +if sys.platform == "darwin": O_EVTONLY: Final[int] O_NOFOLLOW_ANY: Final[int] O_SYMLINK: Final[int] -if sys.platform != "win32" and sys.version_info >= (3, 10): +if sys.platform != "win32" and sys.version_info >= (3, 15): + NODEV: Final[int] + +if sys.platform == "linux" and sys.version_info >= (3, 15): + AT_NO_AUTOMOUNT: Final[int] + AT_STATX_DONT_SYNC: Final[int] + AT_STATX_FORCE_SYNC: Final[int] + AT_STATX_SYNC_AS_STAT: Final[int] + STATX_ATIME: Final[int] + STATX_BASIC_STATS: Final[int] + STATX_BLOCKS: Final[int] + STATX_BTIME: Final[int] + STATX_CTIME: Final[int] + STATX_DIOALIGN: Final[int] + STATX_GID: Final[int] + STATX_INO: Final[int] + STATX_MNT_ID: Final[int] + STATX_MNT_ID_UNIQUE: Final[int] + STATX_MODE: Final[int] + STATX_MTIME: Final[int] + STATX_NLINK: Final[int] + STATX_SIZE: Final[int] + STATX_TYPE: Final[int] + STATX_UID: Final[int] + +if sys.platform != "win32": O_FSYNC: Final[int] if sys.platform != "linux" and sys.platform != "win32" and sys.version_info >= (3, 13): @@ -713,18 +768,21 @@ class _Environ(MutableMapping[AnyStr, AnyStr], Generic[AnyStr]): encodevalue: _EnvironCodeFunc[AnyStr], decodevalue: _EnvironCodeFunc[AnyStr], ) -> None: ... + @overload def get(self, key: AnyStr, default: None = None) -> AnyStr | None: ... @overload def get(self, key: AnyStr, default: AnyStr) -> AnyStr: ... @overload def get(self, key: AnyStr, default: _T) -> AnyStr | _T: ... + @overload def pop(self, key: AnyStr) -> AnyStr: ... @overload def pop(self, key: AnyStr, default: AnyStr) -> AnyStr: ... @overload def pop(self, key: AnyStr, default: _T) -> AnyStr | _T: ... + def setdefault(self, key: AnyStr, value: AnyStr) -> AnyStr: ... def copy(self) -> dict[AnyStr, AnyStr]: ... def __delitem__(self, key: AnyStr) -> None: ... @@ -734,6 +792,7 @@ class _Environ(MutableMapping[AnyStr, AnyStr], Generic[AnyStr]): def __len__(self) -> int: ... def __or__(self, other: Mapping[_T1, _T2]) -> dict[AnyStr | _T1, AnyStr | _T2]: ... def __ror__(self, other: Mapping[_T1, _T2]) -> dict[AnyStr | _T1, AnyStr | _T2]: ... + # We use @overload instead of a Union for reasons similar to those given for # overloading MutableMapping.update in stdlib/typing.pyi # The type: ignore is needed due to incompatible __or__/__ior__ signatures @@ -749,6 +808,9 @@ if sys.platform != "win32": if sys.version_info >= (3, 14): def reload_environ() -> None: ... +if sys.platform == "linux" and sys.version_info >= (3, 15): + def _clearenv() -> None: ... + if sys.version_info >= (3, 11) or sys.platform != "win32": EX_OK: Final[int] @@ -805,8 +867,7 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo # st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime. # # More items may be added at the end by some implementations. - if sys.version_info >= (3, 10): - __match_args__: Final = ("st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size") + __match_args__: Final = ("st_mode", "st_ino", "st_dev", "st_nlink", "st_uid", "st_gid", "st_size") @property def st_mode(self) -> int: ... # protection bits, @@ -890,6 +951,7 @@ def listdir(path: StrPath | None = None) -> list[str]: ... def listdir(path: BytesPath) -> list[bytes]: ... @overload def listdir(path: int) -> list[str]: ... + @final class DirEntry(Generic[AnyStr]): # This is what the scandir iterator yields @@ -911,19 +973,18 @@ class DirEntry(Generic[AnyStr]): @final class statvfs_result(structseq[int], tuple[int, int, int, int, int, int, int, int, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ( - "f_bsize", - "f_frsize", - "f_blocks", - "f_bfree", - "f_bavail", - "f_files", - "f_ffree", - "f_favail", - "f_flag", - "f_namemax", - ) + __match_args__: Final = ( + "f_bsize", + "f_frsize", + "f_blocks", + "f_bfree", + "f_bavail", + "f_files", + "f_ffree", + "f_favail", + "f_flag", + "f_namemax", + ) @property def f_bsize(self) -> int: ... @@ -951,22 +1012,24 @@ class statvfs_result(structseq[int], tuple[int, int, int, int, int, int, int, in # ----- os function stubs ----- def fsencode(filename: StrOrBytesPath) -> bytes: ... def fsdecode(filename: StrOrBytesPath) -> str: ... + @overload def fspath(path: str) -> str: ... @overload def fspath(path: bytes) -> bytes: ... @overload def fspath(path: PathLike[AnyStr]) -> AnyStr: ... + def get_exec_path(env: Mapping[str, str] | None = None) -> list[str]: ... def getlogin() -> str: ... def getpid() -> int: ... def getppid() -> int: ... def strerror(code: int, /) -> str: ... def umask(mask: int, /) -> int: ... + @final class uname_result(structseq[str], tuple[str, str, str, str, str]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("sysname", "nodename", "release", "version", "machine") + __match_args__: Final = ("sysname", "nodename", "release", "version", "machine") @property def sysname(self) -> str: ... @@ -1023,6 +1086,7 @@ if sys.platform != "win32": def getenvb(key: bytes) -> bytes | None: ... @overload def getenvb(key: bytes, default: _T) -> bytes | _T: ... + def putenv(name: StrOrBytesPath, value: StrOrBytesPath, /) -> None: ... def unsetenv(name: StrOrBytesPath, /) -> None: ... @@ -1109,6 +1173,7 @@ def fdopen( closefd: bool = True, opener: _Opener | None = None, ) -> IO[Any]: ... + def close(fd: int) -> None: ... def closerange(fd_low: int, fd_high: int, /) -> None: ... def device_encoding(fd: int) -> str | None: ... @@ -1154,8 +1219,7 @@ if sys.platform != "win32": def preadv(fd: int, buffers: SupportsLenAndGetItem[WriteableBuffer], offset: int, flags: int = 0, /) -> int: ... def pwritev(fd: int, buffers: SupportsLenAndGetItem[ReadableBuffer], offset: int, flags: int = 0, /) -> int: ... if sys.platform != "darwin": - if sys.version_info >= (3, 10): - RWF_APPEND: Final[int] # docs say available on 3.7+, stubtest says otherwise + RWF_APPEND: Final[int] RWF_DSYNC: Final[int] RWF_SYNC: Final[int] RWF_HIPRI: Final[int] @@ -1182,8 +1246,7 @@ if sys.version_info >= (3, 14): @final class terminal_size(structseq[int], tuple[int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("columns", "lines") + __match_args__: Final = ("columns", "lines") @property def columns(self) -> int: ... @@ -1242,7 +1305,11 @@ def mkdir(path: StrOrBytesPath, mode: int = 0o777, *, dir_fd: int | None = None) if sys.platform != "win32": def mkfifo(path: StrOrBytesPath, mode: int = 0o666, *, dir_fd: int | None = None) -> None: ... # Unix only -def makedirs(name: StrOrBytesPath, mode: int = 0o777, exist_ok: bool = False) -> None: ... +if sys.version_info >= (3, 15): + def makedirs(name: StrOrBytesPath, mode: int = 0o777, exist_ok: bool = False, *, parent_mode: int | None = None) -> None: ... + +else: + def makedirs(name: StrOrBytesPath, mode: int = 0o777, exist_ok: bool = False) -> None: ... if sys.platform != "win32": def mknod(path: StrOrBytesPath, mode: int = 0o600, device: int = 0, *, dir_fd: int | None = None) -> None: ... @@ -1260,6 +1327,7 @@ def replace( src: StrOrBytesPath, dst: StrOrBytesPath, *, src_dir_fd: int | None = None, dst_dir_fd: int | None = None ) -> None: ... def rmdir(path: StrOrBytesPath, *, dir_fd: int | None = None) -> None: ... + @final @type_check_only class _ScandirIterator(Generic[AnyStr]): @@ -1276,11 +1344,76 @@ def scandir(path: None = None) -> _ScandirIterator[str]: ... def scandir(path: int) -> _ScandirIterator[str]: ... @overload def scandir(path: GenericPath[AnyStr]) -> _ScandirIterator[AnyStr]: ... + def stat(path: FileDescriptorOrPath, *, dir_fd: int | None = None, follow_symlinks: bool = True) -> stat_result: ... if sys.platform != "win32": def statvfs(path: FileDescriptorOrPath) -> statvfs_result: ... # Unix only +if sys.platform == "linux" and sys.version_info >= (3, 15): + @final + class statx_result: + @property + def stx_mask(self) -> int: ... + @property + def stx_blksize(self) -> int: ... + @property + def stx_attributes(self) -> int: ... + @property + def stx_attributes_mask(self) -> int: ... + @property + def stx_rdev_major(self) -> int: ... + @property + def stx_rdev_minor(self) -> int: ... + @property + def stx_rdev(self) -> int: ... + @property + def stx_dev_major(self) -> int: ... + @property + def stx_dev_minor(self) -> int: ... + @property + def stx_dev(self) -> int: ... + @property + def stx_mode(self) -> int | None: ... + @property + def stx_nlink(self) -> int | None: ... + @property + def stx_uid(self) -> int | None: ... + @property + def stx_gid(self) -> int | None: ... + @property + def stx_ino(self) -> int | None: ... + @property + def stx_size(self) -> int | None: ... + @property + def stx_blocks(self) -> int | None: ... + @property + def stx_atime(self) -> float | None: ... + @property + def stx_atime_ns(self) -> int | None: ... + @property + def stx_btime(self) -> float | None: ... + @property + def stx_btime_ns(self) -> int | None: ... + @property + def stx_ctime(self) -> float | None: ... + @property + def stx_ctime_ns(self) -> int | None: ... + @property + def stx_mtime(self) -> float | None: ... + @property + def stx_mtime_ns(self) -> int | None: ... + @property + def stx_mnt_id(self) -> int | None: ... + @property + def stx_dio_mem_align(self) -> int | None: ... + @property + def stx_dio_offset_align(self) -> int | None: ... + + def statx( + path: FileDescriptorOrPath, mask: int, *, flags: int = 0, dir_fd: int | None = None, follow_symlinks: bool = True + ) -> statx_result: ... + def symlink( src: StrOrBytesPath, dst: StrOrBytesPath, target_is_directory: bool = False, *, dir_fd: int | None = None ) -> None: ... @@ -1324,6 +1457,7 @@ if sys.platform != "win32": follow_symlinks: bool = False, dir_fd: int | None = None, ) -> Iterator[tuple[bytes, list[bytes], list[bytes], int]]: ... + if sys.platform == "linux": def getxattr(path: FileDescriptorOrPath, attribute: StrOrBytesPath, *, follow_symlinks: bool = True) -> bytes: ... def listxattr(path: FileDescriptorOrPath | None = None, *, follow_symlinks: bool = True) -> list[str]: ... @@ -1408,52 +1542,30 @@ class _wrap_close: def write(self, s: str, /) -> int: ... def writelines(self, lines: Iterable[str], /) -> None: ... -if sys.version_info >= (3, 14): - @deprecated("Soft deprecated. Use the subprocess module instead.") - def popen(cmd: str, mode: str = "r", buffering: int = -1) -> _wrap_close: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnl(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnle(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise sig - -else: - def popen(cmd: str, mode: str = "r", buffering: int = -1) -> _wrap_close: ... - def spawnl(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... - def spawnle(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise sig +@deprecated("Soft deprecated. Use the subprocess module instead.") +def popen(cmd: str, mode: str = "r", buffering: int = -1) -> _wrap_close: ... +@deprecated("Soft deprecated. Use the subprocess module instead.") +def spawnl(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... +@deprecated("Soft deprecated. Use the subprocess module instead.") +def spawnle(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise sig if sys.platform != "win32": - if sys.version_info >= (3, 14): - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnv(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnve(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... - - else: - def spawnv(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... - def spawnve(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... - + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnv(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnve(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... else: - if sys.version_info >= (3, 14): - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnv(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, /) -> int: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnve(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, env: _ExecEnv, /) -> int: ... - - else: - def spawnv(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, /) -> int: ... - def spawnve(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, env: _ExecEnv, /) -> int: ... - -if sys.version_info >= (3, 14): @deprecated("Soft deprecated. Use the subprocess module instead.") - def system(command: StrOrBytesPath) -> int: ... + def spawnv(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, /) -> int: ... + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnve(mode: int, path: StrOrBytesPath, argv: _ExecVArgs, env: _ExecEnv, /) -> int: ... -else: - def system(command: StrOrBytesPath) -> int: ... +@deprecated("Soft deprecated. Use the subprocess module instead.") +def system(command: StrOrBytesPath) -> int: ... @final class times_result(structseq[float], tuple[float, float, float, float, float]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("user", "system", "children_user", "children_system", "elapsed") + __match_args__: Final = ("user", "system", "children_user", "children_system", "elapsed") @property def user(self) -> float: ... @@ -1470,41 +1582,25 @@ def times() -> times_result: ... def waitpid(pid: int, options: int, /) -> tuple[int, int]: ... if sys.platform == "win32": - if sys.version_info >= (3, 10): - def startfile( - filepath: StrOrBytesPath, - operation: str = ..., - arguments: str = "", - cwd: StrOrBytesPath | None = None, - show_cmd: int = 1, - ) -> None: ... - else: - def startfile(filepath: StrOrBytesPath, operation: str = ...) -> None: ... + def startfile( + filepath: StrOrBytesPath, operation: str = ..., arguments: str = "", cwd: StrOrBytesPath | None = None, show_cmd: int = 1 + ) -> None: ... else: - if sys.version_info >= (3, 14): - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnlp(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnlpe(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise signature - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnvp(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... - @deprecated("Soft deprecated. Use the subprocess module instead.") - def spawnvpe(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... - - else: - def spawnlp(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... - def spawnlpe(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise signature - def spawnvp(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... - def spawnvpe(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... - + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnlp(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: StrOrBytesPath) -> int: ... + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnlpe(mode: int, file: StrOrBytesPath, arg0: StrOrBytesPath, *args: Any) -> int: ... # Imprecise signature + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnvp(mode: int, file: StrOrBytesPath, args: _ExecVArgs) -> int: ... + @deprecated("Soft deprecated. Use the subprocess module instead.") + def spawnvpe(mode: int, file: StrOrBytesPath, args: _ExecVArgs, env: _ExecEnv) -> int: ... def wait() -> tuple[int, int]: ... # Unix only # Added to MacOS in 3.13 if sys.platform != "darwin" or sys.version_info >= (3, 13): @final class waitid_result(structseq[int], tuple[int, int, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("si_pid", "si_uid", "si_signo", "si_status", "si_code") + __match_args__: Final = ("si_pid", "si_uid", "si_signo", "si_status", "si_code") @property def si_pid(self) -> int: ... @@ -1627,8 +1723,7 @@ else: if sys.platform != "win32": @final class sched_param(structseq[int], tuple[int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("sched_priority",) + __match_args__: Final = ("sched_priority",) def __new__(cls, sched_priority: int) -> Self: ... @property @@ -1719,7 +1814,7 @@ if sys.version_info >= (3, 12) and sys.platform == "win32": def listmounts(volume: str) -> list[str]: ... def listvolumes() -> list[str]: ... -if sys.version_info >= (3, 10) and sys.platform == "linux": +if sys.platform == "linux": EFD_CLOEXEC: Final[int] EFD_NONBLOCK: Final[int] EFD_SEMAPHORE: Final[int] diff --git a/mypy/typeshed/stdlib/parser.pyi b/mypy/typeshed/stdlib/parser.pyi deleted file mode 100644 index 9b287fcc6529d..0000000000000 --- a/mypy/typeshed/stdlib/parser.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from _typeshed import StrOrBytesPath -from collections.abc import Sequence -from types import CodeType -from typing import Any, ClassVar, final - -def expr(source: str) -> STType: ... -def suite(source: str) -> STType: ... -def sequence2st(sequence: Sequence[Any]) -> STType: ... -def tuple2st(sequence: Sequence[Any]) -> STType: ... -def st2list(st: STType, line_info: bool = False, col_info: bool = False) -> list[Any]: ... -def st2tuple(st: STType, line_info: bool = False, col_info: bool = False) -> tuple[Any, ...]: ... -def compilest(st: STType, filename: StrOrBytesPath = ...) -> CodeType: ... -def isexpr(st: STType) -> bool: ... -def issuite(st: STType) -> bool: ... - -class ParserError(Exception): ... - -@final -class STType: - __hash__: ClassVar[None] # type: ignore[assignment] - def compile(self, filename: StrOrBytesPath = ...) -> CodeType: ... - def isexpr(self) -> bool: ... - def issuite(self) -> bool: ... - def tolist(self, line_info: bool = False, col_info: bool = False) -> list[Any]: ... - def totuple(self, line_info: bool = False, col_info: bool = False) -> tuple[Any, ...]: ... diff --git a/mypy/typeshed/stdlib/pathlib/__init__.pyi b/mypy/typeshed/stdlib/pathlib/__init__.pyi index 4f094130665c8..ca0d41f649583 100644 --- a/mypy/typeshed/stdlib/pathlib/__init__.pyi +++ b/mypy/typeshed/stdlib/pathlib/__init__.pyi @@ -29,31 +29,32 @@ if sys.version_info >= (3, 13): __all__ += ["UnsupportedOperation"] class PurePath(PathLike[str]): - if sys.version_info >= (3, 13): - __slots__ = ( - "_raw_paths", - "_drv", - "_root", - "_tail_cached", - "_str", - "_str_normcase_cached", - "_parts_normcase_cached", - "_hash", - ) - elif sys.version_info >= (3, 12): - __slots__ = ( - "_raw_paths", - "_drv", - "_root", - "_tail_cached", - "_str", - "_str_normcase_cached", - "_parts_normcase_cached", - "_lines_cached", - "_hash", - ) - else: - __slots__ = ("_drv", "_root", "_parts", "_str", "_hash", "_pparts", "_cached_cparts") + if sys.version_info < (3, 15): + if sys.version_info >= (3, 13): + __slots__ = ( + "_raw_paths", + "_drv", + "_root", + "_tail_cached", + "_str", + "_str_normcase_cached", + "_parts_normcase_cached", + "_hash", + ) + elif sys.version_info >= (3, 12): + __slots__ = ( + "_raw_paths", + "_drv", + "_root", + "_tail_cached", + "_str", + "_str_normcase_cached", + "_parts_normcase_cached", + "_lines_cached", + "_hash", + ) + else: + __slots__ = ("_drv", "_root", "_parts", "_str", "_hash", "_pparts", "_cached_cparts") if sys.version_info >= (3, 13): parser: ClassVar[types.ModuleType] def full_match(self, pattern: StrPath, *, case_sensitive: bool | None = None) -> bool: ... @@ -82,6 +83,9 @@ class PurePath(PathLike[str]): def __hash__(self) -> int: ... def __fspath__(self) -> str: ... + if sys.version_info >= (3, 15): + def __vfspath__(self) -> str: ... + def __lt__(self, other: PurePath) -> bool: ... def __le__(self, other: PurePath) -> bool: ... def __gt__(self, other: PurePath) -> bool: ... @@ -93,24 +97,23 @@ class PurePath(PathLike[str]): @deprecated("Deprecated since Python 3.14; will be removed in Python 3.19. Use `Path.as_uri()` instead.") def as_uri(self) -> str: ... def is_absolute(self) -> bool: ... - if sys.version_info >= (3, 13): - @deprecated( - "Deprecated since Python 3.13; will be removed in Python 3.15. " - "Use `os.path.isreserved()` to detect reserved paths on Windows." - ) - def is_reserved(self) -> bool: ... - else: - def is_reserved(self) -> bool: ... + if sys.version_info < (3, 15): + if sys.version_info >= (3, 13): + @deprecated( + "Deprecated since Python 3.13; will be removed in Python 3.15. " + "Use `os.path.isreserved()` to detect reserved paths on Windows." + ) + def is_reserved(self) -> bool: ... + else: + def is_reserved(self) -> bool: ... if sys.version_info >= (3, 14): def is_relative_to(self, other: StrPath) -> bool: ... - elif sys.version_info >= (3, 12): + else: @overload def is_relative_to(self, other: StrPath, /) -> bool: ... @overload @deprecated("Passing additional arguments is deprecated since Python 3.12; removed in Python 3.14.") def is_relative_to(self, other: StrPath, /, *_deprecated: StrPath) -> bool: ... - else: - def is_relative_to(self, *other: StrPath) -> bool: ... if sys.version_info >= (3, 12): def match(self, path_pattern: str, *, case_sensitive: bool | None = None) -> bool: ... @@ -151,10 +154,8 @@ class PureWindowsPath(PurePath): class Path(PurePath): if sys.version_info >= (3, 14): __slots__ = ("_info",) - elif sys.version_info >= (3, 10): - __slots__ = () else: - __slots__ = ("_accessor",) + __slots__ = () if sys.version_info >= (3, 12): def __new__(cls, *args: StrPath, **kwargs: Unused) -> Self: ... # pyright: ignore[reportInconsistentConstructor] @@ -163,12 +164,8 @@ class Path(PurePath): @classmethod def cwd(cls) -> Self: ... - if sys.version_info >= (3, 10): - def stat(self, *, follow_symlinks: bool = True) -> stat_result: ... - def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: ... - else: - def stat(self) -> stat_result: ... - def chmod(self, mode: int) -> None: ... + def stat(self, *, follow_symlinks: bool = True) -> stat_result: ... + def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: ... if sys.version_info >= (3, 13): @classmethod @@ -211,23 +208,32 @@ class Path(PurePath): def iterdir(self) -> Generator[Self]: ... def lchmod(self, mode: int) -> None: ... def lstat(self) -> stat_result: ... - def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None: ... + if sys.version_info >= (3, 15): + def mkdir( + self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False, *, parent_mode: int | None = None + ) -> None: ... + else: + def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None: ... if sys.version_info >= (3, 14): @property def info(self) -> PathInfo: ... + @overload def move_into(self, target_dir: _PathT) -> _PathT: ... # type: ignore[overload-overlap] @overload def move_into(self, target_dir: StrPath) -> Self: ... # type: ignore[overload-overlap] + @overload def move(self, target: _PathT) -> _PathT: ... # type: ignore[overload-overlap] @overload def move(self, target: StrPath) -> Self: ... # type: ignore[overload-overlap] + @overload def copy_into(self, target_dir: _PathT, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> _PathT: ... # type: ignore[overload-overlap] @overload def copy_into(self, target_dir: StrPath, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> Self: ... # type: ignore[overload-overlap] + @overload def copy(self, target: _PathT, *, follow_symlinks: bool = True, preserve_metadata: bool = False) -> _PathT: ... # type: ignore[overload-overlap] @overload @@ -314,20 +320,12 @@ class Path(PurePath): def is_mount(self) -> bool: ... def readlink(self) -> Self: ... - - if sys.version_info >= (3, 10): - def rename(self, target: StrPath) -> Self: ... - def replace(self, target: StrPath) -> Self: ... - else: - def rename(self, target: str | PurePath) -> Self: ... - def replace(self, target: str | PurePath) -> Self: ... - + def rename(self, target: StrPath) -> Self: ... + def replace(self, target: StrPath) -> Self: ... def resolve(self, strict: bool = False) -> Self: ... def rmdir(self) -> None: ... def symlink_to(self, target: StrOrBytesPath, target_is_directory: bool = False) -> None: ... - if sys.version_info >= (3, 10): - def hardlink_to(self, target: StrOrBytesPath) -> None: ... - + def hardlink_to(self, target: StrOrBytesPath) -> None: ... def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: ... def unlink(self, missing_ok: bool = False) -> None: ... @classmethod @@ -337,18 +335,12 @@ class Path(PurePath): def read_bytes(self) -> bytes: ... def samefile(self, other_path: StrPath) -> bool: ... def write_bytes(self, data: ReadableBuffer) -> int: ... - if sys.version_info >= (3, 10): - def write_text( - self, data: str, encoding: str | None = None, errors: str | None = None, newline: str | None = None - ) -> int: ... - else: - def write_text(self, data: str, encoding: str | None = None, errors: str | None = None) -> int: ... + def write_text( + self, data: str, encoding: str | None = None, errors: str | None = None, newline: str | None = None + ) -> int: ... if sys.version_info < (3, 12): - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `hardlink_to()` instead.") - def link_to(self, target: StrOrBytesPath) -> None: ... - else: - def link_to(self, target: StrOrBytesPath) -> None: ... + @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `hardlink_to()` instead.") + def link_to(self, target: StrOrBytesPath) -> None: ... if sys.version_info >= (3, 12): def walk( self, top_down: bool = True, on_error: Callable[[OSError], object] | None = None, follow_symlinks: bool = False diff --git a/mypy/typeshed/stdlib/pdb.pyi b/mypy/typeshed/stdlib/pdb.pyi index dc1cf3b280860..289637cb5f048 100644 --- a/mypy/typeshed/stdlib/pdb.pyi +++ b/mypy/typeshed/stdlib/pdb.pyi @@ -1,14 +1,14 @@ import signal import sys +from _typeshed import ReadableBuffer from bdb import Bdb, _Backend from cmd import Cmd from collections.abc import Callable, Iterable, Mapping, Sequence -from inspect import _SourceObjectType from linecache import _ModuleGlobals from rlcompleter import Completer from types import CodeType, FrameType, TracebackType -from typing import IO, Any, ClassVar, Final, Literal, TypeVar -from typing_extensions import ParamSpec, Self, TypeAlias, deprecated +from typing import IO, Any, ClassVar, Final, Literal, ParamSpec, TypeAlias, TypeVar +from typing_extensions import Self, deprecated __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] if sys.version_info >= (3, 14): @@ -22,9 +22,15 @@ line_prefix: Final[str] # undocumented class Restart(Exception): ... -def run(statement: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None: ... -def runeval(expression: str, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> Any: ... -def runctx(statement: str, globals: dict[str, Any], locals: Mapping[str, Any]) -> None: ... +def run( # matches `builtins.exec` + statement: str | ReadableBuffer | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, object] | None = None +) -> None: ... +def runctx( # matches `builtins.exec` + statement: str | ReadableBuffer | CodeType, globals: dict[str, Any], locals: Mapping[str, object] +) -> None: ... +def runeval( # matches `builtins.eval` + expression: str | ReadableBuffer | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, object] | None = None +) -> Any: ... def runcall(func: Callable[_P, _T], *args: _P.args, **kwds: _P.kwargs) -> _T | None: ... if sys.version_info >= (3, 14): @@ -130,7 +136,11 @@ class Pdb(Bdb, Cmd): else: def print_stack_trace(self) -> None: ... - def print_stack_entry(self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> ") -> None: ... + if sys.version_info >= (3, 15): + def print_stack_entry(self, frame_lineno: tuple[FrameType, int], prompt_prefix: str | None = None) -> None: ... + else: + def print_stack_entry(self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> ") -> None: ... + def lookupmodule(self, filename: str) -> str | None: ... if sys.version_info < (3, 11): def _runscript(self, filename: str) -> None: ... @@ -259,10 +269,6 @@ class Pdb(Bdb, Cmd): def find_function(funcname: str, filename: str) -> tuple[str, str, int] | None: ... def main() -> None: ... def help() -> None: ... - -if sys.version_info < (3, 10): - def getsourcelines(obj: _SourceObjectType) -> tuple[list[str], int]: ... - def lasti2lineno(code: CodeType, lasti: int) -> int: ... class _rstr(str): diff --git a/mypy/typeshed/stdlib/pickletools.pyi b/mypy/typeshed/stdlib/pickletools.pyi index 8bbfaba31b671..98353d9608880 100644 --- a/mypy/typeshed/stdlib/pickletools.pyi +++ b/mypy/typeshed/stdlib/pickletools.pyi @@ -1,7 +1,6 @@ import sys from collections.abc import Callable, Iterator, MutableMapping -from typing import IO, Any, Final -from typing_extensions import TypeAlias +from typing import IO, Any, Final, TypeAlias __all__ = ["dis", "genops", "optimize"] diff --git a/mypy/typeshed/stdlib/pkgutil.pyi b/mypy/typeshed/stdlib/pkgutil.pyi index 7c70dcc4c5ab1..365792bed9528 100644 --- a/mypy/typeshed/stdlib/pkgutil.pyi +++ b/mypy/typeshed/stdlib/pkgutil.pyi @@ -39,14 +39,10 @@ if sys.version_info < (3, 12): def __init__(self, fullname: str, file: IO[str], filename: StrOrBytesPath, etc: tuple[str, str, int]) -> None: ... if sys.version_info < (3, 14): - if sys.version_info >= (3, 12): - @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `importlib.util.find_spec()` instead.") - def find_loader(fullname: str) -> LoaderProtocol | None: ... - @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `importlib.util.find_spec()` instead.") - def get_loader(module_or_name: str) -> LoaderProtocol | None: ... - else: - def find_loader(fullname: str) -> LoaderProtocol | None: ... - def get_loader(module_or_name: str) -> LoaderProtocol | None: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `importlib.util.find_spec()` instead.") + def find_loader(fullname: str) -> LoaderProtocol | None: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `importlib.util.find_spec()` instead.") + def get_loader(module_or_name: str) -> LoaderProtocol | None: ... def get_importer(path_item: StrOrBytesPath) -> PathEntryFinderProtocol | None: ... def iter_importers(fullname: str = "") -> Iterator[MetaPathFinderProtocol | PathEntryFinderProtocol]: ... @@ -56,4 +52,9 @@ def walk_packages( path: Iterable[StrOrBytesPath] | None = None, prefix: str = "", onerror: Callable[[str], object] | None = None ) -> Iterator[ModuleInfo]: ... def get_data(package: str, resource: str) -> bytes | None: ... -def resolve_name(name: str) -> Any: ... + +if sys.version_info >= (3, 15): + def resolve_name(name: str, *, strict: bool = False) -> Any: ... + +else: + def resolve_name(name: str) -> Any: ... diff --git a/mypy/typeshed/stdlib/platform.pyi b/mypy/typeshed/stdlib/platform.pyi index 69d702bb155cd..7d837436ef794 100644 --- a/mypy/typeshed/stdlib/platform.pyi +++ b/mypy/typeshed/stdlib/platform.pyi @@ -10,7 +10,7 @@ def mac_ver( release: str = "", versioninfo: tuple[str, str, str] = ("", "", ""), machine: str = "" ) -> tuple[str, tuple[str, str, str], str]: ... -if sys.version_info >= (3, 13): +if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def java_ver( release: str = "", @@ -19,14 +19,6 @@ if sys.version_info >= (3, 13): osinfo: tuple[str, str, str] = ("", "", ""), ) -> tuple[str, str, tuple[str, str, str], tuple[str, str, str]]: ... -else: - def java_ver( - release: str = "", - vendor: str = "", - vminfo: tuple[str, str, str] = ("", "", ""), - osinfo: tuple[str, str, str] = ("", "", ""), - ) -> tuple[str, str, tuple[str, str, str], tuple[str, str, str]]: ... - def system_alias(system: str, release: str, version: str) -> tuple[str, str, str]: ... def architecture(executable: str = sys.executable, bits: str = "", linkage: str = "") -> tuple[str, str]: ... @@ -57,9 +49,7 @@ if sys.version_info >= (3, 12): else: @disjoint_base class uname_result(_uname_result_base): - if sys.version_info >= (3, 10): - __match_args__ = ("system", "node", "release", "version", "machine") # pyright: ignore[reportAssignmentType] - + __match_args__ = ("system", "node", "release", "version", "machine") # pyright: ignore[reportAssignmentType] def __new__(_cls, system: str, node: str, release: str, version: str, machine: str) -> Self: ... @property def processor(self) -> str: ... @@ -79,9 +69,7 @@ def python_revision() -> str: ... def python_build() -> tuple[str, str]: ... def python_compiler() -> str: ... def platform(aliased: bool = False, terse: bool = False) -> str: ... - -if sys.version_info >= (3, 10): - def freedesktop_os_release() -> dict[str, str]: ... +def freedesktop_os_release() -> dict[str, str]: ... if sys.version_info >= (3, 13): class AndroidVer(NamedTuple): diff --git a/mypy/typeshed/stdlib/poplib.pyi b/mypy/typeshed/stdlib/poplib.pyi index f5669ec87e87e..d4555a6b5ec0c 100644 --- a/mypy/typeshed/stdlib/poplib.pyi +++ b/mypy/typeshed/stdlib/poplib.pyi @@ -4,8 +4,8 @@ import sys from _typeshed import StrOrBytesPath from builtins import list as _list # conflicts with a method named "list" from re import Pattern -from typing import Any, BinaryIO, Final, NoReturn, overload -from typing_extensions import TypeAlias, deprecated +from typing import Any, BinaryIO, Final, NoReturn, TypeAlias, overload +from typing_extensions import deprecated __all__ = ["POP3", "error_proto", "POP3_SSL"] @@ -44,10 +44,12 @@ class POP3: timestamp: Pattern[str] def apop(self, user: str, password: str) -> bytes: ... def top(self, which: Any, howmuch: int) -> _LongResp: ... + @overload def uidl(self) -> _LongResp: ... @overload def uidl(self, which: Any) -> bytes: ... + def utf8(self) -> bytes: ... def capa(self) -> dict[str, _list[str]]: ... def stls(self, context: ssl.SSLContext | None = None) -> bytes: ... @@ -83,6 +85,7 @@ class POP3_SSL(POP3): timeout: float = ..., context: None = None, ) -> None: ... + keyfile: StrOrBytesPath | None certfile: StrOrBytesPath | None # "context" is actually the last argument, diff --git a/mypy/typeshed/stdlib/posix.pyi b/mypy/typeshed/stdlib/posix.pyi index 6d0d76ab82176..36bb5be18e88f 100644 --- a/mypy/typeshed/stdlib/posix.pyi +++ b/mypy/typeshed/stdlib/posix.pyi @@ -39,6 +39,7 @@ if sys.platform != "win32": O_DIRECTORY as O_DIRECTORY, O_DSYNC as O_DSYNC, O_EXCL as O_EXCL, + O_FSYNC as O_FSYNC, O_NDELAY as O_NDELAY, O_NOCTTY as O_NOCTTY, O_NOFOLLOW as O_NOFOLLOW, @@ -227,9 +228,6 @@ if sys.platform != "win32": writev as writev, ) - if sys.version_info >= (3, 10): - from os import O_FSYNC as O_FSYNC - if sys.version_info >= (3, 11): from os import login_tty as login_tty @@ -265,6 +263,36 @@ if sys.platform != "win32": if sys.platform != "linux" and sys.version_info >= (3, 13): from os import O_EXEC as O_EXEC, O_SEARCH as O_SEARCH + if sys.version_info >= (3, 15): + from os import NODEV as NODEV + + if sys.version_info >= (3, 15) and sys.platform == "linux": + from os import ( + AT_NO_AUTOMOUNT as AT_NO_AUTOMOUNT, + AT_STATX_DONT_SYNC as AT_STATX_DONT_SYNC, + AT_STATX_FORCE_SYNC as AT_STATX_FORCE_SYNC, + AT_STATX_SYNC_AS_STAT as AT_STATX_SYNC_AS_STAT, + STATX_ATIME as STATX_ATIME, + STATX_BASIC_STATS as STATX_BASIC_STATS, + STATX_BLOCKS as STATX_BLOCKS, + STATX_BTIME as STATX_BTIME, + STATX_CTIME as STATX_CTIME, + STATX_DIOALIGN as STATX_DIOALIGN, + STATX_GID as STATX_GID, + STATX_INO as STATX_INO, + STATX_MNT_ID as STATX_MNT_ID, + STATX_MNT_ID_UNIQUE as STATX_MNT_ID_UNIQUE, + STATX_MODE as STATX_MODE, + STATX_MTIME as STATX_MTIME, + STATX_NLINK as STATX_NLINK, + STATX_SIZE as STATX_SIZE, + STATX_TYPE as STATX_TYPE, + STATX_UID as STATX_UID, + _clearenv as _clearenv, + statx as statx, + statx_result as statx_result, + ) + if sys.platform != "darwin": from os import ( POSIX_FADV_DONTNEED as POSIX_FADV_DONTNEED, @@ -273,6 +301,7 @@ if sys.platform != "win32": POSIX_FADV_RANDOM as POSIX_FADV_RANDOM, POSIX_FADV_SEQUENTIAL as POSIX_FADV_SEQUENTIAL, POSIX_FADV_WILLNEED as POSIX_FADV_WILLNEED, + RWF_APPEND as RWF_APPEND, RWF_DSYNC as RWF_DSYNC, RWF_HIPRI as RWF_HIPRI, RWF_NOWAIT as RWF_NOWAIT, @@ -303,14 +332,14 @@ if sys.platform != "win32": setresuid as setresuid, ) - if sys.version_info >= (3, 10): - from os import RWF_APPEND as RWF_APPEND - if sys.platform != "darwin" or sys.version_info >= (3, 13): from os import waitid as waitid, waitid_result as waitid_result if sys.platform == "linux": from os import ( + EFD_CLOEXEC as EFD_CLOEXEC, + EFD_NONBLOCK as EFD_NONBLOCK, + EFD_SEMAPHORE as EFD_SEMAPHORE, GRND_NONBLOCK as GRND_NONBLOCK, GRND_RANDOM as GRND_RANDOM, MFD_ALLOW_SEALING as MFD_ALLOW_SEALING, @@ -341,10 +370,16 @@ if sys.platform != "win32": SCHED_BATCH as SCHED_BATCH, SCHED_IDLE as SCHED_IDLE, SCHED_RESET_ON_FORK as SCHED_RESET_ON_FORK, + SPLICE_F_MORE as SPLICE_F_MORE, + SPLICE_F_MOVE as SPLICE_F_MOVE, + SPLICE_F_NONBLOCK as SPLICE_F_NONBLOCK, XATTR_CREATE as XATTR_CREATE, XATTR_REPLACE as XATTR_REPLACE, XATTR_SIZE_MAX as XATTR_SIZE_MAX, copy_file_range as copy_file_range, + eventfd as eventfd, + eventfd_read as eventfd_read, + eventfd_write as eventfd_write, getrandom as getrandom, getxattr as getxattr, listxattr as listxattr, @@ -352,22 +387,9 @@ if sys.platform != "win32": pidfd_open as pidfd_open, removexattr as removexattr, setxattr as setxattr, + splice as splice, ) - if sys.version_info >= (3, 10): - from os import ( - EFD_CLOEXEC as EFD_CLOEXEC, - EFD_NONBLOCK as EFD_NONBLOCK, - EFD_SEMAPHORE as EFD_SEMAPHORE, - SPLICE_F_MORE as SPLICE_F_MORE, - SPLICE_F_MOVE as SPLICE_F_MOVE, - SPLICE_F_NONBLOCK as SPLICE_F_NONBLOCK, - eventfd as eventfd, - eventfd_read as eventfd_read, - eventfd_write as eventfd_write, - splice as splice, - ) - if sys.version_info >= (3, 12): from os import ( CLONE_FILES as CLONE_FILES, @@ -390,6 +412,8 @@ if sys.platform != "win32": ) if sys.platform == "darwin": + from os import O_EVTONLY as O_EVTONLY, O_NOFOLLOW_ANY as O_NOFOLLOW_ANY, O_SYMLINK as O_SYMLINK + if sys.version_info >= (3, 12): from os import ( PRIO_DARWIN_BG as PRIO_DARWIN_BG, @@ -397,8 +421,6 @@ if sys.platform != "win32": PRIO_DARWIN_PROCESS as PRIO_DARWIN_PROCESS, PRIO_DARWIN_THREAD as PRIO_DARWIN_THREAD, ) - if sys.platform == "darwin" and sys.version_info >= (3, 10): - from os import O_EVTONLY as O_EVTONLY, O_NOFOLLOW_ANY as O_NOFOLLOW_ANY, O_SYMLINK as O_SYMLINK # Not same as os.environ or os.environb # Because of this variable, we can't do "from posix import *" in os/__init__.pyi diff --git a/mypy/typeshed/stdlib/posixpath.pyi b/mypy/typeshed/stdlib/posixpath.pyi index 84e1b1e028bde..b4068629b698e 100644 --- a/mypy/typeshed/stdlib/posixpath.pyi +++ b/mypy/typeshed/stdlib/posixpath.pyi @@ -17,6 +17,9 @@ from genericpath import ( samestat as samestat, ) +if sys.version_info >= (3, 15): + from genericpath import ALL_BUT_LAST as ALL_BUT_LAST + if sys.version_info >= (3, 13): from genericpath import isdevdrive as isdevdrive from os import PathLike @@ -64,6 +67,8 @@ __all__ = [ "commonpath", ] __all__ += ["ALLOW_MISSING"] +if sys.version_info >= (3, 15): + __all__ += ["ALL_BUT_LAST"] if sys.version_info >= (3, 12): __all__ += ["isjunction", "splitroot"] if sys.version_info >= (3, 13): @@ -85,30 +90,54 @@ devnull: LiteralString def abspath(path: PathLike[AnyStr]) -> AnyStr: ... @overload def abspath(path: AnyStr) -> AnyStr: ... -@overload -def basename(p: PathLike[AnyStr]) -> AnyStr: ... -@overload -def basename(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... -@overload -def dirname(p: PathLike[AnyStr]) -> AnyStr: ... -@overload -def dirname(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + +if sys.version_info >= (3, 15): + @overload + def basename(p: PathLike[AnyStr], /) -> AnyStr: ... + @overload + def basename(p: AnyOrLiteralStr, /) -> AnyOrLiteralStr: ... + + @overload + def dirname(p: PathLike[AnyStr], /) -> AnyStr: ... + @overload + def dirname(p: AnyOrLiteralStr, /) -> AnyOrLiteralStr: ... +else: + @overload + def basename(p: PathLike[AnyStr]) -> AnyStr: ... + @overload + def basename(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + + @overload + def dirname(p: PathLike[AnyStr]) -> AnyStr: ... + @overload + def dirname(p: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + @overload def expanduser(path: PathLike[AnyStr]) -> AnyStr: ... @overload def expanduser(path: AnyStr) -> AnyStr: ... + @overload def expandvars(path: PathLike[AnyStr]) -> AnyStr: ... @overload def expandvars(path: AnyStr) -> AnyStr: ... -@overload -def normcase(s: PathLike[AnyStr]) -> AnyStr: ... -@overload -def normcase(s: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + +if sys.version_info >= (3, 15): + @overload + def normcase(s: PathLike[AnyStr], /) -> AnyStr: ... + @overload + def normcase(s: AnyOrLiteralStr, /) -> AnyOrLiteralStr: ... +else: + @overload + def normcase(s: PathLike[AnyStr]) -> AnyStr: ... + @overload + def normcase(s: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + @overload def normpath(path: PathLike[AnyStr]) -> AnyStr: ... @overload def normpath(path: AnyOrLiteralStr) -> AnyOrLiteralStr: ... + @overload def commonpath(paths: Iterable[LiteralString]) -> LiteralString: ... @overload @@ -116,7 +145,7 @@ def commonpath(paths: Iterable[StrPath]) -> str: ... @overload def commonpath(paths: Iterable[BytesPath]) -> bytes: ... -# First parameter is not actually pos-only, +# First parameter is not actually pos-only before Python 3.15, # but must be defined as pos-only in the stub or cross-platform code doesn't type-check, # as the parameter name is different in ntpath.join() @overload @@ -125,36 +154,77 @@ def join(a: LiteralString, /, *paths: LiteralString) -> LiteralString: ... def join(a: StrPath, /, *paths: StrPath) -> str: ... @overload def join(a: BytesPath, /, *paths: BytesPath) -> bytes: ... -@overload -def realpath(filename: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... -@overload -def realpath(filename: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + +if sys.version_info >= (3, 15): + @overload + def realpath(filename: PathLike[AnyStr], /, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + @overload + def realpath(filename: AnyStr, /, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... +else: + @overload + def realpath(filename: PathLike[AnyStr], *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + @overload + def realpath(filename: AnyStr, *, strict: bool | _AllowMissingType = False) -> AnyStr: ... + @overload def relpath(path: LiteralString, start: LiteralString | None = None) -> LiteralString: ... @overload def relpath(path: BytesPath, start: BytesPath | None = None) -> bytes: ... @overload def relpath(path: StrPath, start: StrPath | None = None) -> str: ... -@overload -def split(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... -@overload -def split(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... -@overload -def splitdrive(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... -@overload -def splitdrive(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... -@overload -def splitext(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... -@overload -def splitext(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... -def isabs(s: StrOrBytesPath) -> bool: ... + +if sys.version_info >= (3, 15): + @overload + def split(p: PathLike[AnyStr], /) -> tuple[AnyStr, AnyStr]: ... + @overload + def split(p: AnyOrLiteralStr, /) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... + + @overload + def splitdrive(p: PathLike[AnyStr], /) -> tuple[AnyStr, AnyStr]: ... + @overload + def splitdrive(p: AnyOrLiteralStr, /) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... +else: + @overload + def split(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... + @overload + def split(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... + + @overload + def splitdrive(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... + @overload + def splitdrive(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... + +if sys.version_info >= (3, 15): + @overload + def splitext(p: PathLike[AnyStr], /) -> tuple[AnyStr, AnyStr]: ... + @overload + def splitext(p: AnyOrLiteralStr, /) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... +else: + @overload + def splitext(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr]: ... + @overload + def splitext(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr]: ... + +if sys.version_info >= (3, 15): + def isabs(s: StrOrBytesPath, /) -> bool: ... + +else: + def isabs(s: StrOrBytesPath) -> bool: ... + def islink(path: FileDescriptorOrPath) -> bool: ... def ismount(path: FileDescriptorOrPath) -> bool: ... def lexists(path: FileDescriptorOrPath) -> bool: ... if sys.version_info >= (3, 12): def isjunction(path: StrOrBytesPath) -> bool: ... - @overload - def splitroot(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr, AnyOrLiteralStr]: ... - @overload - def splitroot(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr, AnyStr]: ... + + if sys.version_info >= (3, 15): + @overload + def splitroot(path: AnyOrLiteralStr, /) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr, AnyOrLiteralStr]: ... + @overload + def splitroot(path: PathLike[AnyStr], /) -> tuple[AnyStr, AnyStr, AnyStr]: ... + else: + @overload + def splitroot(p: AnyOrLiteralStr) -> tuple[AnyOrLiteralStr, AnyOrLiteralStr, AnyOrLiteralStr]: ... + @overload + def splitroot(p: PathLike[AnyStr]) -> tuple[AnyStr, AnyStr, AnyStr]: ... diff --git a/mypy/typeshed/stdlib/pprint.pyi b/mypy/typeshed/stdlib/pprint.pyi index 1e80462e25657..dd29020216574 100644 --- a/mypy/typeshed/stdlib/pprint.pyi +++ b/mypy/typeshed/stdlib/pprint.pyi @@ -5,7 +5,8 @@ from typing import IO __all__ = ["pprint", "pformat", "isreadable", "isrecursive", "saferepr", "PrettyPrinter", "pp"] -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 15): + # The `expand` parameter was added in Python 3.15. def pformat( object: object, indent: int = 1, @@ -13,6 +14,7 @@ if sys.version_info >= (3, 10): depth: int | None = None, *, compact: bool = False, + expand: bool = False, sort_dicts: bool = True, underscore_numbers: bool = False, ) -> str: ... @@ -26,9 +28,11 @@ else: *, compact: bool = False, sort_dicts: bool = True, + underscore_numbers: bool = False, ) -> str: ... -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 15): + # The `expand` parameter was added in Python 3.15. def pp( object: object, stream: IO[str] | None = None, @@ -37,6 +41,7 @@ if sys.version_info >= (3, 10): depth: int | None = None, *, compact: bool = False, + expand: bool = False, sort_dicts: bool = False, underscore_numbers: bool = False, ) -> None: ... @@ -51,9 +56,11 @@ else: *, compact: bool = False, sort_dicts: bool = False, + underscore_numbers: bool = False, ) -> None: ... -if sys.version_info >= (3, 10): +if sys.version_info >= (3, 15): + # The `expand` parameter was added in Python 3.15. def pprint( object: object, stream: IO[str] | None = None, @@ -62,6 +69,7 @@ if sys.version_info >= (3, 10): depth: int | None = None, *, compact: bool = False, + expand: bool = False, sort_dicts: bool = True, underscore_numbers: bool = False, ) -> None: ... @@ -76,6 +84,7 @@ else: *, compact: bool = False, sort_dicts: bool = True, + underscore_numbers: bool = False, ) -> None: ... def isreadable(object: object) -> bool: ... @@ -83,7 +92,8 @@ def isrecursive(object: object) -> bool: ... def saferepr(object: object) -> str: ... class PrettyPrinter: - if sys.version_info >= (3, 10): + if sys.version_info >= (3, 15): + # The `expand` parameter was added in Python 3.15. def __init__( self, indent: int = 1, @@ -92,6 +102,7 @@ class PrettyPrinter: stream: IO[str] | None = None, *, compact: bool = False, + expand: bool = False, sort_dicts: bool = True, underscore_numbers: bool = False, ) -> None: ... @@ -105,6 +116,7 @@ class PrettyPrinter: *, compact: bool = False, sort_dicts: bool = True, + underscore_numbers: bool = False, ) -> None: ... def pformat(self, object: object) -> str: ... @@ -155,5 +167,4 @@ class PrettyPrinter: self, items: list[object], stream: SupportsWrite[str], indent: int, allowance: int, context: dict[int, int], level: int ) -> None: ... def _repr(self, object: object, context: dict[int, int], level: int) -> str: ... - if sys.version_info >= (3, 10): - def _safe_repr(self, object: object, context: dict[int, int], maxlevels: int, level: int) -> tuple[str, bool, bool]: ... + def _safe_repr(self, object: object, context: dict[int, int], maxlevels: int, level: int) -> tuple[str, bool, bool]: ... diff --git a/mypy/typeshed/stdlib/profile.pyi b/mypy/typeshed/stdlib/profile.pyi index 696193d9dc169..06ce9a0e44c82 100644 --- a/mypy/typeshed/stdlib/profile.pyi +++ b/mypy/typeshed/stdlib/profile.pyi @@ -1,7 +1,7 @@ from _typeshed import StrOrBytesPath from collections.abc import Callable, Mapping -from typing import Any, TypeVar -from typing_extensions import ParamSpec, Self, TypeAlias +from typing import Any, ParamSpec, TypeAlias, TypeVar +from typing_extensions import Self __all__ = ["run", "runctx", "Profile"] diff --git a/mypy/typeshed/stdlib/profiling/__init__.pyi b/mypy/typeshed/stdlib/profiling/__init__.pyi new file mode 100644 index 0000000000000..435f5a6cc66c3 --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/__init__.pyi @@ -0,0 +1,3 @@ +from . import sampling as sampling, tracing as tracing + +__all__ = ("tracing", "sampling") diff --git a/mypy/typeshed/stdlib/profiling/sampling/__init__.pyi b/mypy/typeshed/stdlib/profiling/sampling/__init__.pyi new file mode 100644 index 0000000000000..1f8b3f7d98fef --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/__init__.pyi @@ -0,0 +1,17 @@ +from .collector import Collector as Collector +from .gecko_collector import GeckoCollector as GeckoCollector +from .heatmap_collector import HeatmapCollector as HeatmapCollector +from .jsonl_collector import JsonlCollector as JsonlCollector +from .pstats_collector import PstatsCollector as PstatsCollector +from .stack_collector import CollapsedStackCollector as CollapsedStackCollector +from .string_table import StringTable as StringTable + +__all__ = ( + "Collector", + "PstatsCollector", + "CollapsedStackCollector", + "HeatmapCollector", + "GeckoCollector", + "JsonlCollector", + "StringTable", +) diff --git a/mypy/typeshed/stdlib/profiling/sampling/collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/collector.pyi new file mode 100644 index 0000000000000..a72a185a2fb74 --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/collector.pyi @@ -0,0 +1,25 @@ +from _typeshed import StrOrBytesPath +from abc import ABC, abstractmethod +from collections.abc import Sequence +from typing import ClassVar, TypeAlias + +from _remote_debugging import AwaitedInfo, FrameInfo, InterpreterInfo, LocationInfo + +_Location: TypeAlias = int | tuple[int, int, int, int] | LocationInfo | None +_Frame: TypeAlias = FrameInfo | tuple[str, _Location, str, int | None] +_Timestamps: TypeAlias = Sequence[int] | None + +def normalize_location(location: _Location) -> tuple[int, int, int, int]: ... +def extract_lineno(location: _Location) -> int: ... +def filter_internal_frames(frames: Sequence[_Frame]) -> list[_Frame]: ... +def iter_async_frames(awaited_info_list: Sequence[AwaitedInfo]) -> object: ... + +class Collector(ABC): + aggregating: ClassVar[bool] # undocumented + @abstractmethod + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def collect_failed_sample(self) -> None: ... + @abstractmethod + def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/gecko_collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/gecko_collector.pyi new file mode 100644 index 0000000000000..666fd42c3ea5f --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/gecko_collector.pyi @@ -0,0 +1,111 @@ +from _typeshed import Incomplete, StrOrBytesPath, StrPath +from collections.abc import Generator, Sequence +from tempfile import TemporaryDirectory +from typing import Any, ClassVar, Final, TypedDict, type_check_only + +from _remote_debugging import AwaitedInfo, InterpreterInfo + +from .collector import Collector, _Timestamps + +@type_check_only +class _GeckoCategory(TypedDict): + name: str + color: str + subcategories: list[str] + +THREAD_STATUS_HAS_GIL: Final[int] +THREAD_STATUS_ON_CPU: Final[int] +THREAD_STATUS_UNKNOWN: Final[int] +THREAD_STATUS_GIL_REQUESTED: Final[int] +THREAD_STATUS_HAS_EXCEPTION: Final[int] +THREAD_STATUS_MAIN_THREAD: Final[int] + +GECKO_CATEGORIES: Final[list[_GeckoCategory]] + +CATEGORY_OTHER: Final = 0 +CATEGORY_PYTHON: Final = 1 +CATEGORY_NATIVE: Final = 2 +CATEGORY_GC: Final = 3 +CATEGORY_GIL: Final = 4 +CATEGORY_CPU: Final = 5 +CATEGORY_CODE_TYPE: Final = 6 +CATEGORY_OPCODES: Final = 7 +CATEGORY_EXCEPTION: Final = 8 + +DEFAULT_SUBCATEGORY: Final = 0 + +GECKO_FORMAT_VERSION: Final = 32 +GECKO_PREPROCESSED_VERSION: Final = 57 + +RESOURCE_TYPE_LIBRARY: Final = 1 + +FRAME_ADDRESS_NONE: Final = -1 +FRAME_INLINE_DEPTH_ROOT: Final = 0 + +PROCESS_TYPE_MAIN: Final = 0 +STACKWALK_DISABLED: Final = 0 + +DEFAULT_SPILL_BUFFER_BYTES: Final[int] + +class SpillColumn: + path: str + buffer: bytearray + + def __init__(self, directory: StrPath, basename: StrPath, *, buffer_bytes: int | None = None) -> None: ... + # "value" accepts the same types as json.JSONEncoder.encode() + def append(self, value: Any) -> None: ... + def flush(self) -> None: ... + def iter_tokens(self) -> Generator[str]: ... + +class GeckoThreadSpill: + sample_count: int + marker_count: int + def __init__(self, directory: StrPath, tid: int) -> None: ... + def append_sample(self, stack_index: int, time_ms: float) -> None: ... + def append_marker( + self, name_idx: int, start_time: float, end_time: float, phase: int, category: int, data: dict[str, Any] + ) -> None: ... + def prepare_read(self) -> None: ... + +class GeckoCollector(Collector): + aggregating: ClassVar[bool] + + sample_interval_usec: int + skip_idle: bool + opcodes_enabled: bool + start_time: float + + global_strings: list[str] + global_string_map: dict[str, int] + + threads: dict[int, dict[str, Any]] + spill_dir: TemporaryDirectory[str] | None + exported: bool + + libs: list[Incomplete] + + sample_count: int + last_sample_time: float + interval: float + + has_gil_start: dict[Incomplete, Incomplete] + no_gil_start: dict[Incomplete, Incomplete] + on_cpu_start: dict[Incomplete, Incomplete] + off_cpu_start: dict[Incomplete, Incomplete] + python_code_start: dict[Incomplete, Incomplete] + native_code_start: dict[Incomplete, Incomplete] + gil_wait_start: dict[Incomplete, Incomplete] + exception_start: dict[Incomplete, Incomplete] + no_exception_start: dict[Incomplete, Incomplete] + + gc_start_per_thread: dict[int, float] + + initialized_threads: set[Incomplete] + + opcode_state: dict[int, tuple[Incomplete, int, int, str, str, float]] + + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False, opcodes: bool = False) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/heatmap_collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/heatmap_collector.pyi new file mode 100644 index 0000000000000..bd523bc382458 --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/heatmap_collector.pyi @@ -0,0 +1,24 @@ +from _typeshed import StrOrBytesPath +from collections.abc import Sequence + +from _remote_debugging import AwaitedInfo, InterpreterInfo + +from .collector import Collector, _Frame, _Timestamps + +class HeatmapCollector(Collector): + FILE_INDEX_FORMAT: str + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def export(self, output_path: StrOrBytesPath) -> None: ... + def process_frames(self, frames: Sequence[_Frame], thread_id: int, weight: int = 1) -> None: ... + def set_stats( + self, + sample_interval_usec: int, + duration_sec: float, + sample_rate: float, + error_rate: float | None = None, + missed_samples: float | None = None, + **kwargs: object, + ) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/jsonl_collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/jsonl_collector.pyi new file mode 100644 index 0000000000000..3bdc4b81c01dc --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/jsonl_collector.pyi @@ -0,0 +1,15 @@ +from _typeshed import StrOrBytesPath +from collections.abc import Sequence + +from _remote_debugging import AwaitedInfo, InterpreterInfo + +from .collector import _Frame, _Timestamps +from .stack_collector import StackTraceCollector + +class JsonlCollector(StackTraceCollector): + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False, mode: int | None = None) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + def process_frames(self, frames: Sequence[_Frame], _thread_id: int, weight: int = 1) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/pstats_collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/pstats_collector.pyi new file mode 100644 index 0000000000000..178d55a7af8e0 --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/pstats_collector.pyi @@ -0,0 +1,17 @@ +from _typeshed import StrOrBytesPath +from collections.abc import Sequence + +from _remote_debugging import AwaitedInfo, InterpreterInfo + +from .collector import Collector, _Timestamps + +class PstatsCollector(Collector): + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + def create_stats(self) -> None: ... + def print_stats( + self, sort: int = -1, limit: int | None = None, show_summary: bool = True, mode: int | None = None + ) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/stack_collector.pyi b/mypy/typeshed/stdlib/profiling/sampling/stack_collector.pyi new file mode 100644 index 0000000000000..0788e08295ad6 --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/stack_collector.pyi @@ -0,0 +1,39 @@ +from _typeshed import StrOrBytesPath +from abc import ABCMeta +from collections.abc import Sequence + +from _remote_debugging import AwaitedInfo, InterpreterInfo + +from .collector import Collector, _Frame, _Timestamps + +class StackTraceCollector(Collector, metaclass=ABCMeta): + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def process_frames(self, frames: Sequence[_Frame], thread_id: int, weight: int = 1) -> None: ... + +class CollapsedStackCollector(StackTraceCollector): + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def process_frames(self, frames: Sequence[_Frame], thread_id: int, weight: int = 1) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + +class FlamegraphCollector(StackTraceCollector): + def __init__(self, sample_interval_usec: int, *, skip_idle: bool = False) -> None: ... + def collect( + self, stack_frames: Sequence[InterpreterInfo] | Sequence[AwaitedInfo], timestamps_us: _Timestamps = None + ) -> None: ... + def set_stats( + self, + sample_interval_usec: int, + duration_sec: float, + sample_rate: float, + error_rate: float | None = None, + missed_samples: float | None = None, + mode: int | None = None, + ) -> None: ... + def export(self, filename: StrOrBytesPath) -> None: ... + def process_frames(self, frames: Sequence[_Frame], thread_id: int, weight: int = 1) -> None: ... + +class DiffFlamegraphCollector(FlamegraphCollector): + def __init__(self, sample_interval_usec: int, *, baseline_binary_path: StrOrBytesPath, skip_idle: bool = False) -> None: ... diff --git a/mypy/typeshed/stdlib/profiling/sampling/string_table.pyi b/mypy/typeshed/stdlib/profiling/sampling/string_table.pyi new file mode 100644 index 0000000000000..cb71e82ec036d --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/sampling/string_table.pyi @@ -0,0 +1,5 @@ +class StringTable: + def intern(self, string: object) -> int: ... + def get_string(self, index: int) -> str: ... + def get_strings(self) -> list[str]: ... + def __len__(self) -> int: ... diff --git a/mypy/typeshed/stdlib/profiling/tracing.pyi b/mypy/typeshed/stdlib/profiling/tracing.pyi new file mode 100644 index 0000000000000..af4ab508406ac --- /dev/null +++ b/mypy/typeshed/stdlib/profiling/tracing.pyi @@ -0,0 +1,9 @@ +from cProfile import Profile as Profile, run as run, runctx as runctx +from types import CodeType +from typing import TypeAlias + +__all__ = ("run", "runctx", "Profile") + +_Label: TypeAlias = tuple[str, int, str] + +def label(code: str | CodeType) -> _Label: ... # undocumented diff --git a/mypy/typeshed/stdlib/pstats.pyi b/mypy/typeshed/stdlib/pstats.pyi index c1da2aea0fc5a..19eb683df1666 100644 --- a/mypy/typeshed/stdlib/pstats.pyi +++ b/mypy/typeshed/stdlib/pstats.pyi @@ -4,8 +4,8 @@ from collections.abc import Iterable from cProfile import Profile as _cProfile from dataclasses import dataclass from profile import Profile -from typing import IO, Any, Literal, overload -from typing_extensions import Self, TypeAlias +from typing import IO, Any, Literal, TypeAlias, overload +from typing_extensions import Self if sys.version_info >= (3, 11): from enum import StrEnum @@ -72,10 +72,12 @@ class Stats: def add(self, *arg_list: None | str | Profile | _cProfile | Self) -> Self: ... def dump_stats(self, filename: StrOrBytesPath) -> None: ... def get_sort_arg_defs(self) -> _SortArgDict: ... + @overload def sort_stats(self, field: Literal[-1, 0, 1, 2]) -> Self: ... @overload def sort_stats(self, *field: str) -> Self: ... + def reverse_order(self) -> Self: ... def strip_dirs(self) -> Self: ... def calc_callees(self) -> None: ... @@ -86,6 +88,9 @@ class Stats: def print_callees(self, *amount: _Selector) -> Self: ... def print_callers(self, *amount: _Selector) -> Self: ... def print_call_heading(self, name_size: int, column_title: str) -> None: ... + if sys.version_info >= (3, 15): + def print_call_subheading(self, name_size: int) -> None: ... + def print_call_line(self, name_size: int, source: str, call_dict: dict[str, Any], arrow: str = "->") -> None: ... def print_title(self) -> None: ... def print_line(self, func: str) -> None: ... diff --git a/mypy/typeshed/stdlib/pty.pyi b/mypy/typeshed/stdlib/pty.pyi index d1c78f9e3dd67..e74c02ab1e1e6 100644 --- a/mypy/typeshed/stdlib/pty.pyi +++ b/mypy/typeshed/stdlib/pty.pyi @@ -1,7 +1,7 @@ import sys from collections.abc import Callable, Iterable -from typing import Final -from typing_extensions import TypeAlias, deprecated +from typing import Final, TypeAlias +from typing_extensions import deprecated if sys.platform != "win32": __all__ = ["openpty", "fork", "spawn"] @@ -15,14 +15,10 @@ if sys.platform != "win32": def openpty() -> tuple[int, int]: ... if sys.version_info < (3, 14): - if sys.version_info >= (3, 12): - @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `openpty()` instead.") - def master_open() -> tuple[int, str]: ... - @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `openpty()` instead.") - def slave_open(tty_name: str) -> int: ... - else: - def master_open() -> tuple[int, str]: ... - def slave_open(tty_name: str) -> int: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `openpty()` instead.") + def master_open() -> tuple[int, str]: ... + @deprecated("Deprecated since Python 3.12; removed in Python 3.14. Use `openpty()` instead.") + def slave_open(tty_name: str) -> int: ... def fork() -> tuple[int, int]: ... def spawn(argv: str | Iterable[str], master_read: _Reader = ..., stdin_read: _Reader = ...) -> int: ... diff --git a/mypy/typeshed/stdlib/pwd.pyi b/mypy/typeshed/stdlib/pwd.pyi index a84ba324718af..6a7e24f78125d 100644 --- a/mypy/typeshed/stdlib/pwd.pyi +++ b/mypy/typeshed/stdlib/pwd.pyi @@ -5,8 +5,7 @@ from typing import Any, Final, final if sys.platform != "win32": @final class struct_passwd(structseq[Any], tuple[str, str, int, int, str, str, str]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("pw_name", "pw_passwd", "pw_uid", "pw_gid", "pw_gecos", "pw_dir", "pw_shell") + __match_args__: Final = ("pw_name", "pw_passwd", "pw_uid", "pw_gid", "pw_gecos", "pw_dir", "pw_shell") @property def pw_name(self) -> str: ... diff --git a/mypy/typeshed/stdlib/py_compile.pyi b/mypy/typeshed/stdlib/py_compile.pyi index 334ce79b5dd04..e0ee67c5e93fe 100644 --- a/mypy/typeshed/stdlib/py_compile.pyi +++ b/mypy/typeshed/stdlib/py_compile.pyi @@ -1,5 +1,4 @@ import enum -import sys from typing import AnyStr __all__ = ["compile", "main", "PyCompileError", "PycInvalidationMode"] @@ -26,9 +25,4 @@ def compile( invalidation_mode: PycInvalidationMode | None = None, quiet: int = 0, ) -> AnyStr | None: ... - -if sys.version_info >= (3, 10): - def main() -> None: ... - -else: - def main(args: list[str] | None = None) -> int: ... +def main() -> None: ... diff --git a/mypy/typeshed/stdlib/pyclbr.pyi b/mypy/typeshed/stdlib/pyclbr.pyi index 504a5d5f115a0..541f962d33e1c 100644 --- a/mypy/typeshed/stdlib/pyclbr.pyi +++ b/mypy/typeshed/stdlib/pyclbr.pyi @@ -1,4 +1,3 @@ -import sys from collections.abc import Mapping, Sequence __all__ = ["readmodule", "readmodule_ex", "Class", "Function"] @@ -8,44 +7,33 @@ class _Object: name: str file: int lineno: int - - if sys.version_info >= (3, 10): - end_lineno: int | None - + end_lineno: int | None parent: _Object | None # This is a dict at runtime, but we're typing it as Mapping to # avoid variance issues in the subclasses children: Mapping[str, _Object] - if sys.version_info >= (3, 10): - def __init__( - self, module: str, name: str, file: str, lineno: int, end_lineno: int | None, parent: _Object | None - ) -> None: ... - else: - def __init__(self, module: str, name: str, file: str, lineno: int, parent: _Object | None) -> None: ... + def __init__( + self, module: str, name: str, file: str, lineno: int, end_lineno: int | None, parent: _Object | None + ) -> None: ... class Function(_Object): - if sys.version_info >= (3, 10): - is_async: bool - + is_async: bool parent: Function | Class | None children: dict[str, Class | Function] - if sys.version_info >= (3, 10): - def __init__( - self, - module: str, - name: str, - file: str, - lineno: int, - parent: Function | Class | None = None, - is_async: bool = False, - *, - end_lineno: int | None = None, - ) -> None: ... - else: - def __init__(self, module: str, name: str, file: str, lineno: int, parent: Function | Class | None = None) -> None: ... + def __init__( + self, + module: str, + name: str, + file: str, + lineno: int, + parent: Function | Class | None = None, + is_async: bool = False, + *, + end_lineno: int | None = None, + ) -> None: ... class Class(_Object): super: list[Class | str] | None @@ -53,22 +41,17 @@ class Class(_Object): parent: Class | None children: dict[str, Class | Function] - if sys.version_info >= (3, 10): - def __init__( - self, - module: str, - name: str, - super_: list[Class | str] | None, - file: str, - lineno: int, - parent: Class | None = None, - *, - end_lineno: int | None = None, - ) -> None: ... - else: - def __init__( - self, module: str, name: str, super: list[Class | str] | None, file: str, lineno: int, parent: Class | None = None - ) -> None: ... + def __init__( + self, + module: str, + name: str, + super_: list[Class | str] | None, + file: str, + lineno: int, + parent: Class | None = None, + *, + end_lineno: int | None = None, + ) -> None: ... def readmodule(module: str, path: Sequence[str] | None = None) -> dict[str, Class]: ... def readmodule_ex(module: str, path: Sequence[str] | None = None) -> dict[str, Class | Function | list[str]]: ... diff --git a/mypy/typeshed/stdlib/pydoc.pyi b/mypy/typeshed/stdlib/pydoc.pyi index f8129ac20ade7..2e423dd3d425e 100644 --- a/mypy/typeshed/stdlib/pydoc.pyi +++ b/mypy/typeshed/stdlib/pydoc.pyi @@ -1,12 +1,12 @@ import sys -from _typeshed import OptExcInfo, SupportsWrite, Unused +from _typeshed import OptExcInfo, StrPath, SupportsWrite, Unused from abc import abstractmethod from builtins import list as _list # "list" conflicts with method name from collections.abc import Callable, Container, Mapping, MutableMapping from reprlib import Repr from types import MethodType, ModuleType, TracebackType -from typing import IO, Any, AnyStr, Final, NoReturn, Protocol, TypeVar, overload, type_check_only -from typing_extensions import TypeGuard, deprecated +from typing import IO, Any, AnyStr, Final, NoReturn, Protocol, TypeGuard, TypeVar, overload, type_check_only +from typing_extensions import deprecated __all__ = ["help"] @@ -32,14 +32,8 @@ def stripid(text: str) -> str: ... def allmethods(cl: type) -> MutableMapping[str, MethodType]: ... def visiblename(name: str, all: Container[str] | None = None, obj: object = None) -> bool: ... def classify_class_attrs(object: object) -> list[tuple[str, str, type, str]]: ... - -if sys.version_info >= (3, 13): - @deprecated("Deprecated since Python 3.13.") - def ispackage(path: str) -> bool: ... # undocumented - -else: - def ispackage(path: str) -> bool: ... # undocumented - +@deprecated("Deprecated since Python 3.13.") +def ispackage(path: StrPath) -> bool: ... # undocumented def source_synopsis(file: IO[AnyStr]) -> AnyStr | None: ... def synopsis(filename: str, cache: MutableMapping[str, tuple[int, str]] = {}) -> str | None: ... @@ -62,6 +56,9 @@ def safeimport(path: str, forceload: bool = ..., cache: MutableMapping[str, Modu class Doc: PYTHONDOCS: str + if sys.version_info >= (3, 15): + STDLIB_DIR: str + def document(self, object: object, name: str | None = None, *args: Any) -> str: ... def fail(self, object: object, name: str | None = None, *args: Any) -> NoReturn: ... @abstractmethod @@ -76,7 +73,10 @@ class Doc: def docproperty(self, object: object, name: str | None = None, *args: Any) -> str: ... @abstractmethod def docdata(self, object: object, name: str | None = None, *args: Any) -> str: ... - def getdocloc(self, object: object, basedir: str = ...) -> str | None: ... + if sys.version_info >= (3, 15): + def getdocloc(self, object: object, basedir: str | None = None) -> str | None: ... + else: + def getdocloc(self, object: object, basedir: str = ...) -> str | None: ... class HTMLRepr(Repr): def __init__(self) -> None: ... diff --git a/mypy/typeshed/stdlib/pyexpat/__init__.pyi b/mypy/typeshed/stdlib/pyexpat/__init__.pyi index bc522d5f3c92d..83841a226a53c 100644 --- a/mypy/typeshed/stdlib/pyexpat/__init__.pyi +++ b/mypy/typeshed/stdlib/pyexpat/__init__.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import ReadableBuffer, SupportsRead from collections.abc import Callable from pyexpat import errors as errors, model as model -from typing import Any, Final, final -from typing_extensions import CapsuleType, TypeAlias +from typing import Any, Final, TypeAlias, final +from typing_extensions import CapsuleType from xml.parsers.expat import ExpatError as ExpatError EXPAT_VERSION: Final[str] # undocumented @@ -30,10 +30,13 @@ class XMLParserType: def UseForeignDTD(self, flag: bool = True, /) -> None: ... def GetReparseDeferralEnabled(self) -> bool: ... def SetReparseDeferralEnabled(self, enabled: bool, /) -> None: ... - if sys.version_info >= (3, 10): - # Added in Python 3.10.20, 3.11.15, 3.12.3, 3.13.10, 3.14.1 - def SetAllocTrackerActivationThreshold(self, threshold: int, /) -> None: ... - def SetAllocTrackerMaximumAmplification(self, max_factor: float, /) -> None: ... + # Added in Python 3.10.20, 3.11.15, 3.12.3, 3.13.10, 3.14.1 + def SetAllocTrackerActivationThreshold(self, threshold: int, /) -> None: ... + def SetAllocTrackerMaximumAmplification(self, max_factor: float, /) -> None: ... + if sys.version_info >= (3, 13): + # Added in Python 3.13.4, 3.14.6 + def SetBillionLaughsAttackProtectionActivationThreshold(self, threshold: int, /) -> None: ... + def SetBillionLaughsAttackProtectionMaximumAmplification(self, max_factor: float, /) -> None: ... @property def intern(self) -> dict[str, str]: ... diff --git a/mypy/typeshed/stdlib/random.pyi b/mypy/typeshed/stdlib/random.pyi index 08619bf66351a..80fd2c7628a52 100644 --- a/mypy/typeshed/stdlib/random.pyi +++ b/mypy/typeshed/stdlib/random.pyi @@ -4,7 +4,7 @@ from _typeshed import SupportsLenAndGetItem from collections.abc import Callable, Iterable, MutableSequence, Sequence, Set as AbstractSet from fractions import Fraction from typing import Any, ClassVar, NoReturn, TypeVar, overload -from typing_extensions import Self, deprecated +from typing_extensions import deprecated __all__ = [ "Random", @@ -45,9 +45,6 @@ class Random(_random.Random): # Using other `seed` types is deprecated since 3.9 and removed in 3.11 # Ignore Y041, since random.seed doesn't treat int like a float subtype. Having an explicit # int better documents conventional usage of random.seed. - if sys.version_info < (3, 10): - # this is a workaround for pyright correctly flagging an inconsistent inherited constructor, see #14624 - def __new__(cls, x: int | float | str | bytes | bytearray | None = None) -> Self: ... # noqa: Y041 def seed(self, a: int | float | str | bytes | bytearray | None = None, version: int = 2) -> None: ... # type: ignore[override] # noqa: Y041 def getstate(self) -> tuple[Any, ...]: ... @@ -72,6 +69,7 @@ class Random(_random.Random): @overload @deprecated("The `random` parameter is deprecated since Python 3.9; removed in Python 3.11.") def shuffle(self, x: MutableSequence[Any], random: Callable[[], float] | None = None) -> None: ... + if sys.version_info >= (3, 11): def sample(self, population: Sequence[_T], k: int, *, counts: Iterable[int] | None = None) -> list[_T]: ... else: diff --git a/mypy/typeshed/stdlib/re.pyi b/mypy/typeshed/stdlib/re.pyi index fb2a06d5e4c81..0183d7a813318 100644 --- a/mypy/typeshed/stdlib/re.pyi +++ b/mypy/typeshed/stdlib/re.pyi @@ -1,12 +1,10 @@ import enum -import sre_compile -import sre_constants import sys from _typeshed import MaybeNone, ReadableBuffer from collections.abc import Callable, Iterator, Mapping from types import GenericAlias -from typing import Any, AnyStr, Final, Generic, Literal, TypeVar, final, overload -from typing_extensions import TypeAlias, deprecated +from typing import Any, AnyStr, Final, Generic, Literal, TypeAlias, TypeVar, final, overload +from typing_extensions import deprecated __all__ = [ "match", @@ -38,6 +36,8 @@ __all__ = [ "Match", "Pattern", ] +if sys.version_info >= (3, 15): + __all__ += ["prefixmatch"] if sys.version_info < (3, 13): __all__ += ["template"] @@ -47,8 +47,6 @@ if sys.version_info >= (3, 11): if sys.version_info >= (3, 13): __all__ += ["PatternError"] - PatternError = sre_constants.error - _T = TypeVar("_T") # The implementation defines this in re._constants (version_info >= 3, 11) or @@ -61,6 +59,9 @@ class error(Exception): colno: int def __init__(self, msg: str, pattern: str | bytes | None = None, pos: int | None = None) -> None: ... +if sys.version_info >= (3, 13): + PatternError = error + @final class Match(Generic[AnyStr]): @property @@ -78,12 +79,14 @@ class Match(Generic[AnyStr]): # this match instance. @property def re(self) -> Pattern[AnyStr]: ... + @overload def expand(self: Match[str], template: str) -> str: ... @overload def expand(self: Match[bytes], template: ReadableBuffer) -> bytes: ... @overload def expand(self, template: AnyStr) -> AnyStr: ... + # group() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload def group(self, group: Literal[0] = 0, /) -> AnyStr: ... @@ -91,28 +94,33 @@ class Match(Generic[AnyStr]): def group(self, group: str | int, /) -> AnyStr | MaybeNone: ... @overload def group(self, group1: str | int, group2: str | int, /, *groups: str | int) -> tuple[AnyStr | MaybeNone, ...]: ... + # Each item of groups()'s return tuple is either "AnyStr" or # "AnyStr | None", depending on the pattern. @overload def groups(self) -> tuple[AnyStr | MaybeNone, ...]: ... @overload def groups(self, default: _T) -> tuple[AnyStr | _T, ...]: ... + # Each value in groupdict()'s return dict is either "AnyStr" or # "AnyStr | None", depending on the pattern. @overload def groupdict(self) -> dict[str, AnyStr | MaybeNone]: ... @overload def groupdict(self, default: _T) -> dict[str, AnyStr | _T]: ... + def start(self, group: int | str = 0, /) -> int: ... def end(self, group: int | str = 0, /) -> int: ... def span(self, group: int | str = 0, /) -> tuple[int, int]: ... @property def regs(self) -> tuple[tuple[int, int], ...]: ... # undocumented + # __getitem__() returns "AnyStr" or "AnyStr | None", depending on the pattern. @overload def __getitem__(self, key: Literal[0], /) -> AnyStr: ... @overload def __getitem__(self, key: int | str, /) -> AnyStr | MaybeNone: ... + def __copy__(self) -> Match[AnyStr]: ... def __deepcopy__(self, memo: Any, /) -> Match[AnyStr]: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -127,18 +135,24 @@ class Pattern(Generic[AnyStr]): def groups(self) -> int: ... @property def pattern(self) -> AnyStr: ... + @overload def search(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload def search(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... @overload def search(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... + @overload def match(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload def match(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> Match[bytes] | None: ... @overload def match(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... + + if sys.version_info >= (3, 15): + prefixmatch = match + @overload def fullmatch(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Match[str] | None: ... @overload @@ -147,12 +161,14 @@ class Pattern(Generic[AnyStr]): ) -> Match[bytes] | None: ... @overload def fullmatch(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Match[AnyStr] | None: ... + @overload def split(self: Pattern[str], string: str, maxsplit: int = 0) -> list[str | MaybeNone]: ... @overload def split(self: Pattern[bytes], string: ReadableBuffer, maxsplit: int = 0) -> list[bytes | MaybeNone]: ... @overload def split(self, string: AnyStr, maxsplit: int = 0) -> list[AnyStr | MaybeNone]: ... + # return type depends on the number of groups in the pattern @overload def findall(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> list[Any]: ... @@ -160,6 +176,7 @@ class Pattern(Generic[AnyStr]): def findall(self: Pattern[bytes], string: ReadableBuffer, pos: int = 0, endpos: int = sys.maxsize) -> list[Any]: ... @overload def findall(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> list[AnyStr]: ... + @overload def finditer(self: Pattern[str], string: str, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[str]]: ... @overload @@ -168,6 +185,7 @@ class Pattern(Generic[AnyStr]): ) -> Iterator[Match[bytes]]: ... @overload def finditer(self, string: AnyStr, pos: int = 0, endpos: int = sys.maxsize) -> Iterator[Match[AnyStr]]: ... + @overload def sub(self: Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0) -> str: ... @overload @@ -179,6 +197,7 @@ class Pattern(Generic[AnyStr]): ) -> bytes: ... @overload def sub(self, repl: AnyStr | Callable[[Match[AnyStr]], AnyStr], string: AnyStr, count: int = 0) -> AnyStr: ... + @overload def subn(self: Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0) -> tuple[str, int]: ... @overload @@ -190,6 +209,7 @@ class Pattern(Generic[AnyStr]): ) -> tuple[bytes, int]: ... @overload def subn(self, repl: AnyStr | Callable[[Match[AnyStr]], AnyStr], string: AnyStr, count: int = 0) -> tuple[AnyStr, int]: ... + def __copy__(self) -> Pattern[AnyStr]: ... def __deepcopy__(self, memo: Any, /) -> Pattern[AnyStr]: ... def __eq__(self, value: object, /) -> bool: ... @@ -199,23 +219,23 @@ class Pattern(Generic[AnyStr]): # ----- re variables and constants ----- class RegexFlag(enum.IntFlag): - A = sre_compile.SRE_FLAG_ASCII + A = 256 ASCII = A - DEBUG = sre_compile.SRE_FLAG_DEBUG - I = sre_compile.SRE_FLAG_IGNORECASE + DEBUG = 128 + I = 2 IGNORECASE = I - L = sre_compile.SRE_FLAG_LOCALE + L = 4 LOCALE = L - M = sre_compile.SRE_FLAG_MULTILINE + M = 8 MULTILINE = M - S = sre_compile.SRE_FLAG_DOTALL + S = 16 DOTALL = S - X = sre_compile.SRE_FLAG_VERBOSE + X = 64 VERBOSE = X - U = sre_compile.SRE_FLAG_UNICODE + U = 32 UNICODE = U if sys.version_info < (3, 13): - T = sre_compile.SRE_FLAG_TEMPLATE + T = 1 TEMPLATE = T if sys.version_info >= (3, 11): NOFLAG = 0 @@ -253,32 +273,45 @@ _FlagsType: TypeAlias = int | RegexFlag def compile(pattern: AnyStr, flags: _FlagsType = 0) -> Pattern[AnyStr]: ... @overload def compile(pattern: Pattern[AnyStr], flags: _FlagsType = 0) -> Pattern[AnyStr]: ... + @overload def search(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> Match[str] | None: ... @overload def search(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> Match[bytes] | None: ... + @overload def match(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> Match[str] | None: ... @overload def match(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> Match[bytes] | None: ... + +if sys.version_info >= (3, 15): + @overload + def prefixmatch(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> Match[str] | None: ... + @overload + def prefixmatch(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> Match[bytes] | None: ... + @overload def fullmatch(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> Match[str] | None: ... @overload def fullmatch(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> Match[bytes] | None: ... + @overload def split(pattern: str | Pattern[str], string: str, maxsplit: int = 0, flags: _FlagsType = 0) -> list[str | MaybeNone]: ... @overload def split( pattern: bytes | Pattern[bytes], string: ReadableBuffer, maxsplit: int = 0, flags: _FlagsType = 0 ) -> list[bytes | MaybeNone]: ... + @overload def findall(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> list[Any]: ... @overload def findall(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> list[Any]: ... + @overload def finditer(pattern: str | Pattern[str], string: str, flags: _FlagsType = 0) -> Iterator[Match[str]]: ... @overload def finditer(pattern: bytes | Pattern[bytes], string: ReadableBuffer, flags: _FlagsType = 0) -> Iterator[Match[bytes]]: ... + @overload def sub( pattern: str | Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0, flags: _FlagsType = 0 @@ -291,6 +324,7 @@ def sub( count: int = 0, flags: _FlagsType = 0, ) -> bytes: ... + @overload def subn( pattern: str | Pattern[str], repl: str | Callable[[Match[str]], str], string: str, count: int = 0, flags: _FlagsType = 0 @@ -303,12 +337,10 @@ def subn( count: int = 0, flags: _FlagsType = 0, ) -> tuple[bytes, int]: ... + def escape(pattern: AnyStr) -> AnyStr: ... def purge() -> None: ... if sys.version_info < (3, 13): - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11; removed in Python 3.13. Use `re.compile()` instead.") - def template(pattern: AnyStr | Pattern[AnyStr], flags: _FlagsType = 0) -> Pattern[AnyStr]: ... # undocumented - else: - def template(pattern: AnyStr | Pattern[AnyStr], flags: _FlagsType = 0) -> Pattern[AnyStr]: ... # undocumented + @deprecated("Deprecated since Python 3.11; removed in Python 3.13. Use `re.compile()` instead.") + def template(pattern: AnyStr | Pattern[AnyStr], flags: _FlagsType = 0) -> Pattern[AnyStr]: ... # undocumented diff --git a/mypy/typeshed/stdlib/readline.pyi b/mypy/typeshed/stdlib/readline.pyi index 7325c267b32c2..aff1e504a174a 100644 --- a/mypy/typeshed/stdlib/readline.pyi +++ b/mypy/typeshed/stdlib/readline.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import StrOrBytesPath from collections.abc import Callable, Sequence -from typing import Literal -from typing_extensions import TypeAlias +from typing import Literal, TypeAlias if sys.platform != "win32": _Completer: TypeAlias = Callable[[str, int], str | None] diff --git a/mypy/typeshed/stdlib/reprlib.pyi b/mypy/typeshed/stdlib/reprlib.pyi index 68ada65693485..d990c2708ae95 100644 --- a/mypy/typeshed/stdlib/reprlib.pyi +++ b/mypy/typeshed/stdlib/reprlib.pyi @@ -2,8 +2,7 @@ import sys from array import array from collections import deque from collections.abc import Callable -from typing import Any -from typing_extensions import TypeAlias +from typing import Any, TypeAlias __all__ = ["Repr", "repr", "recursive_repr"] diff --git a/mypy/typeshed/stdlib/resource.pyi b/mypy/typeshed/stdlib/resource.pyi index f99cd5b088056..7e72b28cc6dd1 100644 --- a/mypy/typeshed/stdlib/resource.pyi +++ b/mypy/typeshed/stdlib/resource.pyi @@ -15,6 +15,9 @@ if sys.platform != "win32": RLIMIT_RSS: Final[int] RLIMIT_STACK: Final[int] RLIM_INFINITY: Final[int] + if sys.version_info >= (3, 15): + RLIM_SAVED_CUR: Final[int] + RLIM_SAVED_MAX: Final[int] RUSAGE_CHILDREN: Final[int] RUSAGE_SELF: Final[int] if sys.platform == "linux": @@ -25,30 +28,34 @@ if sys.platform != "win32": RLIMIT_RTTIME: Final[int] RLIMIT_SIGPENDING: Final[int] RUSAGE_THREAD: Final[int] + if sys.version_info >= (3, 15) and sys.platform != "linux" and sys.platform != "darwin": + RLIMIT_NTHR: Final[int] + RLIMIT_PIPEBUF: Final[int] + RLIMIT_THREADS: Final[int] + RLIMIT_UMTXP: Final[int] @final class struct_rusage( structseq[float], tuple[float, float, int, int, int, int, int, int, int, int, int, int, int, int, int, int] ): - if sys.version_info >= (3, 10): - __match_args__: Final = ( - "ru_utime", - "ru_stime", - "ru_maxrss", - "ru_ixrss", - "ru_idrss", - "ru_isrss", - "ru_minflt", - "ru_majflt", - "ru_nswap", - "ru_inblock", - "ru_oublock", - "ru_msgsnd", - "ru_msgrcv", - "ru_nsignals", - "ru_nvcsw", - "ru_nivcsw", - ) + __match_args__: Final = ( + "ru_utime", + "ru_stime", + "ru_maxrss", + "ru_ixrss", + "ru_idrss", + "ru_isrss", + "ru_minflt", + "ru_majflt", + "ru_nswap", + "ru_inblock", + "ru_oublock", + "ru_msgsnd", + "ru_msgrcv", + "ru_nsignals", + "ru_nvcsw", + "ru_nivcsw", + ) @property def ru_utime(self) -> float: ... diff --git a/mypy/typeshed/stdlib/sched.pyi b/mypy/typeshed/stdlib/sched.pyi index 436d9984ee4da..54eb6ed0cd537 100644 --- a/mypy/typeshed/stdlib/sched.pyi +++ b/mypy/typeshed/stdlib/sched.pyi @@ -1,33 +1,18 @@ -import sys import time from collections.abc import Callable -from typing import Any, ClassVar, NamedTuple, type_check_only -from typing_extensions import TypeAlias +from typing import Any, NamedTuple, TypeAlias __all__ = ["scheduler"] _ActionCallback: TypeAlias = Callable[..., Any] -if sys.version_info >= (3, 10): - class Event(NamedTuple): - time: float - priority: Any - sequence: int - action: _ActionCallback - argument: tuple[Any, ...] - kwargs: dict[str, Any] - -else: - @type_check_only - class _EventBase(NamedTuple): - time: float - priority: Any - action: _ActionCallback - argument: tuple[Any, ...] - kwargs: dict[str, Any] - - class Event(_EventBase): - __hash__: ClassVar[None] # type: ignore[assignment] +class Event(NamedTuple): + time: float + priority: Any + sequence: int + action: _ActionCallback + argument: tuple[Any, ...] + kwargs: dict[str, Any] class scheduler: timefunc: Callable[[], float] diff --git a/mypy/typeshed/stdlib/select.pyi b/mypy/typeshed/stdlib/select.pyi index d2b1d6d676e57..ad93cb0b7055b 100644 --- a/mypy/typeshed/stdlib/select.pyi +++ b/mypy/typeshed/stdlib/select.pyi @@ -24,6 +24,7 @@ if sys.platform != "win32": # This is actually a function that returns an instance of a class. # The class is not accessible directly, and also calls itself select.poll. + @final class poll: # default value is select.POLLIN | select.POLLPRI | select.POLLOUT def register(self, fd: FileDescriptorLike, eventmask: int = 7, /) -> None: ... @@ -31,9 +32,9 @@ if sys.platform != "win32": def unregister(self, fd: FileDescriptorLike, /) -> None: ... def poll(self, timeout: float | None = None, /) -> list[tuple[int, int]]: ... -_R = TypeVar("_R", default=Never) -_W = TypeVar("_W", default=Never) -_X = TypeVar("_X", default=Never) +_R = TypeVar("_R", default=Never, bound=FileDescriptorLike) +_W = TypeVar("_W", default=Never, bound=FileDescriptorLike) +_X = TypeVar("_X", default=Never, bound=FileDescriptorLike) def select( rlist: Iterable[_R], wlist: Iterable[_W], xlist: Iterable[_X], timeout: float | None = None, / @@ -120,6 +121,7 @@ if sys.platform == "linux": "Use `os.set_inheritable()` to make the file descriptor inheritable." ) def __new__(self, sizehint: int = -1, flags: int = 0) -> Self: ... + def __enter__(self) -> Self: ... def __exit__( self, @@ -158,6 +160,7 @@ if sys.platform == "linux": if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win32": # Solaris only + @final class devpoll: def close(self) -> None: ... closed: bool diff --git a/mypy/typeshed/stdlib/selectors.pyi b/mypy/typeshed/stdlib/selectors.pyi index bcca4e341b9a1..4a3478a67552a 100644 --- a/mypy/typeshed/stdlib/selectors.pyi +++ b/mypy/typeshed/stdlib/selectors.pyi @@ -3,9 +3,7 @@ from _typeshed import FileDescriptor, FileDescriptorLike, Unused from abc import ABCMeta, abstractmethod from collections.abc import Mapping from typing import Any, Final, NamedTuple -from typing_extensions import Self, TypeAlias - -_EventMask: TypeAlias = int +from typing_extensions import Self EVENT_READ: Final = 1 EVENT_WRITE: Final = 2 @@ -13,17 +11,17 @@ EVENT_WRITE: Final = 2 class SelectorKey(NamedTuple): fileobj: FileDescriptorLike fd: FileDescriptor - events: _EventMask + events: int data: Any class BaseSelector(metaclass=ABCMeta): @abstractmethod - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... + def register(self, fileobj: FileDescriptorLike, events: int, data: Any = None) -> SelectorKey: ... @abstractmethod def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def modify(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... + def modify(self, fileobj: FileDescriptorLike, events: int, data: Any = None) -> SelectorKey: ... @abstractmethod - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, int]]: ... def close(self) -> None: ... def get_key(self, fileobj: FileDescriptorLike) -> SelectorKey: ... @abstractmethod @@ -32,16 +30,16 @@ class BaseSelector(metaclass=ABCMeta): def __exit__(self, *args: Unused) -> None: ... class _BaseSelectorImpl(BaseSelector, metaclass=ABCMeta): - def register(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... + def register(self, fileobj: FileDescriptorLike, events: int, data: Any = None) -> SelectorKey: ... def unregister(self, fileobj: FileDescriptorLike) -> SelectorKey: ... - def modify(self, fileobj: FileDescriptorLike, events: _EventMask, data: Any = None) -> SelectorKey: ... + def modify(self, fileobj: FileDescriptorLike, events: int, data: Any = None) -> SelectorKey: ... def get_map(self) -> Mapping[FileDescriptorLike, SelectorKey]: ... class SelectSelector(_BaseSelectorImpl): - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, int]]: ... class _PollLikeSelector(_BaseSelectorImpl): - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, int]]: ... if sys.platform != "win32": class PollSelector(_PollLikeSelector): ... @@ -58,12 +56,12 @@ if sys.platform != "linux" and sys.platform != "darwin" and sys.platform != "win if sys.platform != "win32" and sys.platform != "linux": class KqueueSelector(_BaseSelectorImpl): def fileno(self) -> int: ... - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, int]]: ... # Not a real class at runtime, it is just a conditional alias to other real selectors. # The runtime logic is more fine-grained than a `sys.platform` check; # not really expressible in the stubs class DefaultSelector(_BaseSelectorImpl): - def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, _EventMask]]: ... + def select(self, timeout: float | None = None) -> list[tuple[SelectorKey, int]]: ... if sys.platform != "win32": def fileno(self) -> int: ... diff --git a/mypy/typeshed/stdlib/shelve.pyi b/mypy/typeshed/stdlib/shelve.pyi index 654c2ea097f78..c599b0af61384 100644 --- a/mypy/typeshed/stdlib/shelve.pyi +++ b/mypy/typeshed/stdlib/shelve.pyi @@ -1,28 +1,53 @@ import sys from _typeshed import StrOrBytesPath -from collections.abc import Iterator, MutableMapping +from collections.abc import Callable, Iterator, MutableMapping from dbm import _TFlags from types import TracebackType from typing import Any, TypeVar, overload from typing_extensions import Self __all__ = ["Shelf", "BsdDbShelf", "DbfilenameShelf", "open"] +if sys.version_info >= (3, 15): + __all__ += ["ShelveError"] _T = TypeVar("_T") _VT = TypeVar("_VT") +if sys.version_info >= (3, 15): + class ShelveError(Exception): ... + class Shelf(MutableMapping[str, _VT]): - def __init__( - self, dict: MutableMapping[bytes, bytes], protocol: int | None = None, writeback: bool = False, keyencoding: str = "utf-8" - ) -> None: ... + if sys.version_info >= (3, 15): + def __init__( + self, + dict: MutableMapping[bytes, bytes], + protocol: int | None = None, + writeback: bool = False, + keyencoding: str = "utf-8", + *, + serializer: Callable[[Any], bytes] | None = None, + deserializer: Callable[[bytes], Any] | None = None, + ) -> None: ... + + else: + def __init__( + self, + dict: MutableMapping[bytes, bytes], + protocol: int | None = None, + writeback: bool = False, + keyencoding: str = "utf-8", + ) -> None: ... + def __iter__(self) -> Iterator[str]: ... def __len__(self) -> int: ... + @overload # type: ignore[override] def get(self, key: str, default: None = None) -> _VT | None: ... @overload def get(self, key: str, default: _VT) -> _VT: ... @overload def get(self, key: str, default: _T) -> _VT | _T: ... + def __getitem__(self, key: str) -> _VT: ... def __setitem__(self, key: str, value: _VT) -> None: ... def __delitem__(self, key: str) -> None: ... @@ -34,6 +59,8 @@ class Shelf(MutableMapping[str, _VT]): def __del__(self) -> None: ... def close(self) -> None: ... def sync(self) -> None: ... + if sys.version_info >= (3, 15): + def reorganize(self) -> None: ... class BsdDbShelf(Shelf[_VT]): def set_location(self, key: str) -> tuple[str, _VT]: ... @@ -43,14 +70,38 @@ class BsdDbShelf(Shelf[_VT]): def last(self) -> tuple[str, _VT]: ... class DbfilenameShelf(Shelf[_VT]): - if sys.version_info >= (3, 11): + if sys.version_info >= (3, 15): + def __init__( + self, + filename: StrOrBytesPath, + flag: _TFlags = "c", + protocol: int | None = None, + writeback: bool = False, + *, + serializer: Callable[[Any], bytes] | None = None, + deserializer: Callable[[bytes], Any] | None = None, + ) -> None: ... + + elif sys.version_info >= (3, 11): def __init__( self, filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False ) -> None: ... + else: def __init__(self, filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> None: ... -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 15): + def open( + filename: StrOrBytesPath, + flag: _TFlags = "c", + protocol: int | None = None, + writeback: bool = False, + *, + serializer: Callable[[Any], bytes] | None = None, + deserializer: Callable[[bytes], Any] | None = None, + ) -> Shelf[Any]: ... + +elif sys.version_info >= (3, 11): def open( filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False ) -> Shelf[Any]: ... diff --git a/mypy/typeshed/stdlib/shutil.pyi b/mypy/typeshed/stdlib/shutil.pyi index cc26cfc556a00..badb0bae220dc 100644 --- a/mypy/typeshed/stdlib/shutil.pyi +++ b/mypy/typeshed/stdlib/shutil.pyi @@ -3,8 +3,8 @@ import sys from _typeshed import BytesPath, ExcInfo, FileDescriptorOrPath, MaybeNone, StrOrBytesPath, StrPath, SupportsRead, SupportsWrite from collections.abc import Callable, Iterable, Sequence from tarfile import _TarfileFilter -from typing import Any, AnyStr, NamedTuple, NoReturn, Protocol, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias, deprecated +from typing import Any, AnyStr, NamedTuple, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import deprecated __all__ = [ "copyfileobj", @@ -57,14 +57,17 @@ def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: def copyfile(src: StrOrBytesPath, dst: _StrOrBytesPathT, *, follow_symlinks: bool = True) -> _StrOrBytesPathT: ... def copymode(src: StrOrBytesPath, dst: StrOrBytesPath, *, follow_symlinks: bool = True) -> None: ... def copystat(src: StrOrBytesPath, dst: StrOrBytesPath, *, follow_symlinks: bool = True) -> None: ... + @overload def copy(src: StrPath, dst: _StrPathT, *, follow_symlinks: bool = True) -> _StrPathT | str: ... @overload def copy(src: BytesPath, dst: _BytesPathT, *, follow_symlinks: bool = True) -> _BytesPathT | bytes: ... + @overload def copy2(src: StrPath, dst: _StrPathT, *, follow_symlinks: bool = True) -> _StrPathT | str: ... @overload def copy2(src: BytesPath, dst: _BytesPathT, *, follow_symlinks: bool = True) -> _BytesPathT | bytes: ... + def ignore_patterns(*patterns: StrPath) -> Callable[[Any, list[str]], set[str]]: ... def copytree( src: StrPath, @@ -175,7 +178,6 @@ if sys.version_info >= (3, 13): def chown( path: FileDescriptorOrPath, user: str | int, group: str | int, *, dir_fd: int | None = None, follow_symlinks: bool = True ) -> None: ... - else: @overload def chown(path: FileDescriptorOrPath, user: str | int, group: None = None) -> None: ... @@ -195,6 +197,7 @@ if sys.platform == "win32" and sys.version_info < (3, 12): def which(cmd: StrPath, mode: int = 1, path: StrPath | None = None) -> str | None: ... @overload def which(cmd: bytes, mode: int = 1, path: StrPath | None = None) -> bytes | None: ... + def make_archive( base_name: str, format: str, @@ -207,6 +210,7 @@ def make_archive( logger: Any | None = None, ) -> str: ... def get_archive_formats() -> list[tuple[str, str]]: ... + @overload def register_archive_format( name: str, function: Callable[..., object], extra_args: Sequence[tuple[str, Any] | list[Any]], description: str = "" @@ -215,10 +219,12 @@ def register_archive_format( def register_archive_format( name: str, function: Callable[[str, str], object], extra_args: None = None, description: str = "" ) -> None: ... + def unregister_archive_format(name: str) -> None: ... def unpack_archive( filename: StrPath, extract_dir: StrPath | None = None, format: str | None = None, *, filter: _TarfileFilter | None = None ) -> None: ... + @overload def register_unpack_format( name: str, @@ -231,6 +237,7 @@ def register_unpack_format( def register_unpack_format( name: str, extensions: list[str], function: Callable[[str, str], object], extra_args: None = None, description: str = "" ) -> None: ... + def unregister_unpack_format(name: str) -> None: ... def get_unpack_formats() -> list[tuple[str, list[str], str]]: ... def get_terminal_size(fallback: tuple[int, int] = (80, 24)) -> os.terminal_size: ... diff --git a/mypy/typeshed/stdlib/signal.pyi b/mypy/typeshed/stdlib/signal.pyi index 3bf1d31293787..a6a8853671e23 100644 --- a/mypy/typeshed/stdlib/signal.pyi +++ b/mypy/typeshed/stdlib/signal.pyi @@ -3,8 +3,8 @@ from _typeshed import structseq from collections.abc import Callable, Iterable from enum import IntEnum from types import FrameType -from typing import Any, Final, final -from typing_extensions import Never, TypeAlias +from typing import Any, Final, TypeAlias, final +from typing_extensions import Never NSIG: int @@ -69,14 +69,8 @@ _SIGNUM: TypeAlias = int | Signals _HANDLER: TypeAlias = Callable[[int, FrameType | None], Any] | int | Handlers | None def default_int_handler(signalnum: int, frame: FrameType | None, /) -> Never: ... - -if sys.version_info >= (3, 10): # arguments changed in 3.10.2 - def getsignal(signalnum: _SIGNUM) -> _HANDLER: ... - def signal(signalnum: _SIGNUM, handler: _HANDLER) -> _HANDLER: ... - -else: - def getsignal(signalnum: _SIGNUM, /) -> _HANDLER: ... - def signal(signalnum: _SIGNUM, handler: _HANDLER, /) -> _HANDLER: ... +def getsignal(signalnum: _SIGNUM) -> _HANDLER: ... +def signal(signalnum: _SIGNUM, handler: _HANDLER) -> _HANDLER: ... SIGABRT: Final = Signals.SIGABRT SIGFPE: Final = Signals.SIGFPE @@ -135,18 +129,11 @@ else: def getitimer(which: int, /) -> tuple[float, float]: ... def pause() -> None: ... def pthread_kill(thread_id: int, signalnum: int, /) -> None: ... - if sys.version_info >= (3, 10): # arguments changed in 3.10.2 - def pthread_sigmask(how: int, mask: Iterable[int]) -> set[_SIGNUM]: ... - else: - def pthread_sigmask(how: int, mask: Iterable[int], /) -> set[_SIGNUM]: ... - + def pthread_sigmask(how: int, mask: Iterable[int]) -> set[_SIGNUM]: ... def setitimer(which: int, seconds: float, interval: float = 0.0, /) -> tuple[float, float]: ... def siginterrupt(signalnum: int, flag: bool, /) -> None: ... def sigpending() -> Any: ... - if sys.version_info >= (3, 10): # argument changed in 3.10.2 - def sigwait(sigset: Iterable[int]) -> _SIGNUM: ... - else: - def sigwait(sigset: Iterable[int], /) -> _SIGNUM: ... + def sigwait(sigset: Iterable[int]) -> _SIGNUM: ... if sys.platform != "darwin": SIGCLD: Final = Signals.SIGCHLD # alias SIGPOLL: Final = Signals.SIGIO # alias @@ -158,8 +145,7 @@ else: @final class struct_siginfo(structseq[int], tuple[int, int, int, int, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("si_signo", "si_code", "si_errno", "si_pid", "si_uid", "si_status", "si_band") + __match_args__: Final = ("si_signo", "si_code", "si_errno", "si_pid", "si_uid", "si_status", "si_band") @property def si_signo(self) -> int: ... diff --git a/mypy/typeshed/stdlib/site.pyi b/mypy/typeshed/stdlib/site.pyi index 6e39677aaea0e..46e82b0655c10 100644 --- a/mypy/typeshed/stdlib/site.pyi +++ b/mypy/typeshed/stdlib/site.pyi @@ -10,6 +10,16 @@ USER_BASE: str | None def main() -> None: ... def abs_paths() -> None: ... # undocumented def addpackage(sitedir: StrPath, name: StrPath, known_paths: set[str] | None) -> set[str] | None: ... # undocumented + +if sys.version_info >= (3, 15): + class StartupState: + __slots__ = ("_known_paths", "_processed_sitedirs", "_path_entries", "_importexecs", "_entrypoints") + def __init__(self, known_paths: set[str] | None = None) -> None: ... + def addsitedir(self, sitedir: str) -> None: ... + def addusersitepackages(self) -> None: ... + def addsitepackages(self, prefixes: Iterable[str] | None = None) -> None: ... + def process(self) -> None: ... + def addsitedir(sitedir: str, known_paths: set[str] | None = None) -> None: ... def addsitepackages(known_paths: set[str] | None, prefixes: Iterable[str] | None = None) -> set[str] | None: ... # undocumented def addusersitepackages(known_paths: set[str] | None) -> set[str] | None: ... # undocumented diff --git a/mypy/typeshed/stdlib/smtpd.pyi b/mypy/typeshed/stdlib/smtpd.pyi index dee7e949f42fa..cc9ac391a441f 100644 --- a/mypy/typeshed/stdlib/smtpd.pyi +++ b/mypy/typeshed/stdlib/smtpd.pyi @@ -3,8 +3,8 @@ import asyncore import socket import sys from collections import defaultdict -from typing import Any -from typing_extensions import TypeAlias, deprecated +from typing import Any, TypeAlias +from typing_extensions import deprecated if sys.version_info >= (3, 11): __all__ = ["SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy"] diff --git a/mypy/typeshed/stdlib/smtplib.pyi b/mypy/typeshed/stdlib/smtplib.pyi index 74b5ea2cb6fce..1aaa5b49664b0 100644 --- a/mypy/typeshed/stdlib/smtplib.pyi +++ b/mypy/typeshed/stdlib/smtplib.pyi @@ -7,8 +7,8 @@ from re import Pattern from socket import socket from ssl import SSLContext from types import TracebackType -from typing import Any, Final, Protocol, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import Any, Final, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self, deprecated __all__ = [ "SMTPException", @@ -65,6 +65,7 @@ class SMTPAuthenticationError(SMTPResponseException): ... def quoteaddr(addrstring: str) -> str: ... def quotedata(data: str) -> str: ... + @type_check_only class _AuthObject(Protocol): @overload @@ -121,10 +122,12 @@ class SMTP: user: str password: str def auth(self, mechanism: str, authobject: _AuthObject, *, initial_response_ok: bool = True) -> _Reply: ... + @overload def auth_cram_md5(self, challenge: None = None) -> None: ... @overload def auth_cram_md5(self, challenge: ReadableBuffer) -> str: ... + def auth_plain(self, challenge: ReadableBuffer | None = None) -> str: ... def auth_login(self, challenge: ReadableBuffer | None = None) -> str: ... def login(self, user: str, password: str, *, initial_response_ok: bool = True) -> _Reply: ... @@ -203,6 +206,7 @@ class SMTP_SSL(SMTP): source_address: _SourceAddress | None = None, context: None = None, ) -> None: ... + keyfile: StrOrBytesPath | None certfile: StrOrBytesPath | None diff --git a/mypy/typeshed/stdlib/socket.pyi b/mypy/typeshed/stdlib/socket.pyi index a55273a8772c9..87e2bd353730e 100644 --- a/mypy/typeshed/stdlib/socket.pyi +++ b/mypy/typeshed/stdlib/socket.pyi @@ -28,6 +28,7 @@ from _socket import ( IP_MULTICAST_LOOP as IP_MULTICAST_LOOP, IP_MULTICAST_TTL as IP_MULTICAST_TTL, IP_OPTIONS as IP_OPTIONS, + IP_RECVTOS as IP_RECVTOS, IP_TOS as IP_TOS, IP_TTL as IP_TTL, IPPORT_RESERVED as IPPORT_RESERVED, @@ -223,6 +224,7 @@ __all__ = [ "IP_MULTICAST_LOOP", "IP_MULTICAST_TTL", "IP_OPTIONS", + "IP_RECVTOS", "IP_TOS", "IP_TTL", "MSG_CTRUNC", @@ -357,11 +359,6 @@ if sys.platform != "darwin": __all__ += ["TCP_KEEPIDLE", "AF_IRDA", "MSG_ERRQUEUE"] -if sys.version_info >= (3, 10): - from _socket import IP_RECVTOS as IP_RECVTOS - - __all__ += ["IP_RECVTOS"] - if sys.platform != "win32" and sys.platform != "darwin": from _socket import ( IP_TRANSPARENT as IP_TRANSPARENT, @@ -525,7 +522,7 @@ if sys.platform != "darwin": if sys.platform != "darwin" and sys.platform != "linux": __all__ += ["BDADDR_ANY", "BDADDR_LOCAL", "BTPROTO_RFCOMM"] -if sys.platform == "darwin" and sys.version_info >= (3, 10): +if sys.platform == "darwin": from _socket import TCP_KEEPALIVE as TCP_KEEPALIVE __all__ += ["TCP_KEEPALIVE"] @@ -779,6 +776,68 @@ if sys.platform == "linux": from _socket import CAN_RAW_ERR_FILTER as CAN_RAW_ERR_FILTER __all__ += ["CAN_RAW_ERR_FILTER"] + if sys.version_info >= (3, 15): + from _socket import ( + CAN_ISOTP_CHK_PAD_DATA as CAN_ISOTP_CHK_PAD_DATA, + CAN_ISOTP_CHK_PAD_LEN as CAN_ISOTP_CHK_PAD_LEN, + CAN_ISOTP_DEFAULT_EXT_ADDRESS as CAN_ISOTP_DEFAULT_EXT_ADDRESS, + CAN_ISOTP_DEFAULT_FLAGS as CAN_ISOTP_DEFAULT_FLAGS, + CAN_ISOTP_DEFAULT_FRAME_TXTIME as CAN_ISOTP_DEFAULT_FRAME_TXTIME, + CAN_ISOTP_DEFAULT_LL_MTU as CAN_ISOTP_DEFAULT_LL_MTU, + CAN_ISOTP_DEFAULT_LL_TX_DL as CAN_ISOTP_DEFAULT_LL_TX_DL, + CAN_ISOTP_DEFAULT_LL_TX_FLAGS as CAN_ISOTP_DEFAULT_LL_TX_FLAGS, + CAN_ISOTP_DEFAULT_PAD_CONTENT as CAN_ISOTP_DEFAULT_PAD_CONTENT, + CAN_ISOTP_DEFAULT_RECV_BS as CAN_ISOTP_DEFAULT_RECV_BS, + CAN_ISOTP_DEFAULT_RECV_STMIN as CAN_ISOTP_DEFAULT_RECV_STMIN, + CAN_ISOTP_DEFAULT_RECV_WFTMAX as CAN_ISOTP_DEFAULT_RECV_WFTMAX, + CAN_ISOTP_EXTEND_ADDR as CAN_ISOTP_EXTEND_ADDR, + CAN_ISOTP_FORCE_RXSTMIN as CAN_ISOTP_FORCE_RXSTMIN, + CAN_ISOTP_FORCE_TXSTMIN as CAN_ISOTP_FORCE_TXSTMIN, + CAN_ISOTP_HALF_DUPLEX as CAN_ISOTP_HALF_DUPLEX, + CAN_ISOTP_LISTEN_MODE as CAN_ISOTP_LISTEN_MODE, + CAN_ISOTP_LL_OPTS as CAN_ISOTP_LL_OPTS, + CAN_ISOTP_OPTS as CAN_ISOTP_OPTS, + CAN_ISOTP_RECV_FC as CAN_ISOTP_RECV_FC, + CAN_ISOTP_RX_EXT_ADDR as CAN_ISOTP_RX_EXT_ADDR, + CAN_ISOTP_RX_PADDING as CAN_ISOTP_RX_PADDING, + CAN_ISOTP_RX_STMIN as CAN_ISOTP_RX_STMIN, + CAN_ISOTP_SF_BROADCAST as CAN_ISOTP_SF_BROADCAST, + CAN_ISOTP_TX_PADDING as CAN_ISOTP_TX_PADDING, + CAN_ISOTP_TX_STMIN as CAN_ISOTP_TX_STMIN, + CAN_ISOTP_WAIT_TX_DONE as CAN_ISOTP_WAIT_TX_DONE, + SOL_CAN_ISOTP as SOL_CAN_ISOTP, + ) + + __all__ += [ + "CAN_ISOTP_CHK_PAD_DATA", + "CAN_ISOTP_CHK_PAD_LEN", + "CAN_ISOTP_DEFAULT_EXT_ADDRESS", + "CAN_ISOTP_DEFAULT_FLAGS", + "CAN_ISOTP_DEFAULT_FRAME_TXTIME", + "CAN_ISOTP_DEFAULT_LL_MTU", + "CAN_ISOTP_DEFAULT_LL_TX_DL", + "CAN_ISOTP_DEFAULT_LL_TX_FLAGS", + "CAN_ISOTP_DEFAULT_PAD_CONTENT", + "CAN_ISOTP_DEFAULT_RECV_BS", + "CAN_ISOTP_DEFAULT_RECV_STMIN", + "CAN_ISOTP_DEFAULT_RECV_WFTMAX", + "CAN_ISOTP_EXTEND_ADDR", + "CAN_ISOTP_FORCE_RXSTMIN", + "CAN_ISOTP_FORCE_TXSTMIN", + "CAN_ISOTP_HALF_DUPLEX", + "CAN_ISOTP_LL_OPTS", + "CAN_ISOTP_LISTEN_MODE", + "CAN_ISOTP_OPTS", + "CAN_ISOTP_RECV_FC", + "CAN_ISOTP_RX_EXT_ADDR", + "CAN_ISOTP_RX_PADDING", + "CAN_ISOTP_RX_STMIN", + "CAN_ISOTP_SF_BROADCAST", + "CAN_ISOTP_TX_PADDING", + "CAN_ISOTP_TX_STMIN", + "CAN_ISOTP_WAIT_TX_DONE", + "SOL_CAN_ISOTP", + ] if sys.platform == "linux": from _socket import ( @@ -842,7 +901,7 @@ if sys.platform == "linux": "UDPLITE_RECV_CSCOV", "UDPLITE_SEND_CSCOV", ] -if sys.platform == "linux" and sys.version_info >= (3, 10): +if sys.platform == "linux": from _socket import IPPROTO_MPTCP as IPPROTO_MPTCP __all__ += ["IPPROTO_MPTCP"] @@ -1044,6 +1103,12 @@ if sys.platform != "linux": __all__ += ["IPPROTO_GGP", "IPPROTO_IPV4", "IPPROTO_MAX", "IPPROTO_ND", "IP_RECVDSTADDR", "SO_USELOOPBACK"] +if sys.version_info >= (3, 15): + if sys.platform == "win32" or sys.platform == "linux": + from _socket import IPV6_HDRINCL as IPV6_HDRINCL + + __all__ += ["IPV6_HDRINCL"] + if sys.version_info >= (3, 14): from _socket import IP_RECVTTL as IP_RECVTTL @@ -1068,8 +1133,75 @@ if sys.version_info >= (3, 14): if sys.platform == "linux": from _socket import ( + BDADDR_BREDR as BDADDR_BREDR, + BDADDR_LE_PUBLIC as BDADDR_LE_PUBLIC, + BDADDR_LE_RANDOM as BDADDR_LE_RANDOM, + BT_CHANNEL_POLICY as BT_CHANNEL_POLICY, + BT_CHANNEL_POLICY_BREDR_ONLY as BT_CHANNEL_POLICY_BREDR_ONLY, + BT_CHANNEL_POLICY_BREDR_PREFERRED as BT_CHANNEL_POLICY_BREDR_PREFERRED, + BT_CODEC as BT_CODEC, + BT_DEFER_SETUP as BT_DEFER_SETUP, + BT_FLUSHABLE as BT_FLUSHABLE, + BT_FLUSHABLE_OFF as BT_FLUSHABLE_OFF, + BT_FLUSHABLE_ON as BT_FLUSHABLE_ON, + BT_ISO_QOS as BT_ISO_QOS, + BT_MODE as BT_MODE, + BT_MODE_BASIC as BT_MODE_BASIC, + BT_MODE_ERTM as BT_MODE_ERTM, + BT_MODE_EXT_FLOWCTL as BT_MODE_EXT_FLOWCTL, + BT_MODE_LE_FLOWCTL as BT_MODE_LE_FLOWCTL, + BT_MODE_STREAMING as BT_MODE_STREAMING, + BT_PHY as BT_PHY, + BT_PHY_BR_1M_1SLOT as BT_PHY_BR_1M_1SLOT, + BT_PHY_BR_1M_3SLOT as BT_PHY_BR_1M_3SLOT, + BT_PHY_BR_1M_5SLOT as BT_PHY_BR_1M_5SLOT, + BT_PHY_EDR_2M_1SLOT as BT_PHY_EDR_2M_1SLOT, + BT_PHY_EDR_2M_3SLOT as BT_PHY_EDR_2M_3SLOT, + BT_PHY_EDR_2M_5SLOT as BT_PHY_EDR_2M_5SLOT, + BT_PHY_EDR_3M_1SLOT as BT_PHY_EDR_3M_1SLOT, + BT_PHY_EDR_3M_3SLOT as BT_PHY_EDR_3M_3SLOT, + BT_PHY_EDR_3M_5SLOT as BT_PHY_EDR_3M_5SLOT, + BT_PHY_LE_1M_RX as BT_PHY_LE_1M_RX, + BT_PHY_LE_1M_TX as BT_PHY_LE_1M_TX, + BT_PHY_LE_2M_RX as BT_PHY_LE_2M_RX, + BT_PHY_LE_2M_TX as BT_PHY_LE_2M_TX, + BT_PHY_LE_CODED_RX as BT_PHY_LE_CODED_RX, + BT_PHY_LE_CODED_TX as BT_PHY_LE_CODED_TX, + BT_PKT_STATUS as BT_PKT_STATUS, + BT_POWER as BT_POWER, + BT_POWER_FORCE_ACTIVE_OFF as BT_POWER_FORCE_ACTIVE_OFF, + BT_POWER_FORCE_ACTIVE_ON as BT_POWER_FORCE_ACTIVE_ON, + BT_RCVMTU as BT_RCVMTU, + BT_SECURITY as BT_SECURITY, + BT_SECURITY_FIPS as BT_SECURITY_FIPS, + BT_SECURITY_HIGH as BT_SECURITY_HIGH, + BT_SECURITY_LOW as BT_SECURITY_LOW, + BT_SECURITY_MEDIUM as BT_SECURITY_MEDIUM, + BT_SECURITY_SDP as BT_SECURITY_SDP, + BT_SNDMTU as BT_SNDMTU, + BT_VOICE as BT_VOICE, + BT_VOICE_CVSD_16BIT as BT_VOICE_CVSD_16BIT, + BT_VOICE_TRANSPARENT as BT_VOICE_TRANSPARENT, + BT_VOICE_TRANSPARENT_16BIT as BT_VOICE_TRANSPARENT_16BIT, + HCI_CHANNEL_CONTROL as HCI_CHANNEL_CONTROL, + HCI_CHANNEL_LOGGING as HCI_CHANNEL_LOGGING, + HCI_CHANNEL_MONITOR as HCI_CHANNEL_MONITOR, + HCI_CHANNEL_RAW as HCI_CHANNEL_RAW, + HCI_CHANNEL_USER as HCI_CHANNEL_USER, + HCI_DEV_NONE as HCI_DEV_NONE, IP_FREEBIND as IP_FREEBIND, IP_RECVORIGDSTADDR as IP_RECVORIGDSTADDR, + L2CAP_LM as L2CAP_LM, + L2CAP_LM_AUTH as L2CAP_LM_AUTH, + L2CAP_LM_ENCRYPT as L2CAP_LM_ENCRYPT, + L2CAP_LM_MASTER as L2CAP_LM_MASTER, + L2CAP_LM_RELIABLE as L2CAP_LM_RELIABLE, + L2CAP_LM_SECURE as L2CAP_LM_SECURE, + L2CAP_LM_TRUSTED as L2CAP_LM_TRUSTED, + SOL_BLUETOOTH as SOL_BLUETOOTH, + SOL_L2CAP as SOL_L2CAP, + SOL_RFCOMM as SOL_RFCOMM, + SOL_SCO as SOL_SCO, VMADDR_CID_LOCAL as VMADDR_CID_LOCAL, ) @@ -1087,10 +1219,7 @@ error = OSError class herror(error): ... class gaierror(error): ... -if sys.version_info >= (3, 10): - timeout = TimeoutError -else: - class timeout(error): ... +timeout = TimeoutError class AddressFamily(IntEnum): AF_INET = 2 @@ -1325,6 +1454,7 @@ class socket(_socket.socket): def __exit__(self, *args: Unused) -> None: ... def dup(self) -> Self: ... def accept(self) -> tuple[socket, _RetAddress]: ... + # Note that the makefile's documented windows-specific behavior is not represented # mode strings with duplicates are intentionally excluded @overload @@ -1387,6 +1517,7 @@ class socket(_socket.socket): errors: str | None = None, newline: str | None = None, ) -> TextIOWrapper: ... + def sendfile(self, file: _SendableFile, offset: int = 0, count: int | None = None) -> int: ... @property def family(self) -> AddressFamily: ... diff --git a/mypy/typeshed/stdlib/socketserver.pyi b/mypy/typeshed/stdlib/socketserver.pyi index f321d14a792b2..05e0025d6a15b 100644 --- a/mypy/typeshed/stdlib/socketserver.pyi +++ b/mypy/typeshed/stdlib/socketserver.pyi @@ -5,8 +5,8 @@ from _typeshed import ReadableBuffer from collections.abc import Callable from io import BufferedIOBase from socket import socket as _socket -from typing import Any, ClassVar -from typing_extensions import Self, TypeAlias +from typing import Any, ClassVar, TypeAlias +from typing_extensions import Self __all__ = [ "BaseServer", diff --git a/mypy/typeshed/stdlib/spwd.pyi b/mypy/typeshed/stdlib/spwd.pyi index 3a5d39997dcc7..0a06cdfeef642 100644 --- a/mypy/typeshed/stdlib/spwd.pyi +++ b/mypy/typeshed/stdlib/spwd.pyi @@ -5,18 +5,17 @@ from typing import Any, Final, final if sys.platform != "win32": @final class struct_spwd(structseq[Any], tuple[str, str, int, int, int, int, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ( - "sp_namp", - "sp_pwdp", - "sp_lstchg", - "sp_min", - "sp_max", - "sp_warn", - "sp_inact", - "sp_expire", - "sp_flag", - ) + __match_args__: Final = ( + "sp_namp", + "sp_pwdp", + "sp_lstchg", + "sp_min", + "sp_max", + "sp_warn", + "sp_inact", + "sp_expire", + "sp_flag", + ) @property def sp_namp(self) -> str: ... diff --git a/mypy/typeshed/stdlib/sqlite3/__init__.pyi b/mypy/typeshed/stdlib/sqlite3/__init__.pyi index ec37eed8c9277..7bf020558199e 100644 --- a/mypy/typeshed/stdlib/sqlite3/__init__.pyi +++ b/mypy/typeshed/stdlib/sqlite3/__init__.pyi @@ -62,12 +62,15 @@ from sqlite3.dbapi2 import ( threadsafety as threadsafety, ) from types import TracebackType -from typing import Any, Literal, Protocol, SupportsIndex, TypeVar, final, overload, type_check_only -from typing_extensions import Self, TypeAlias, disjoint_base +from typing import Any, Literal, Protocol, SupportsIndex, TypeAlias, TypeVar, final, overload, type_check_only +from typing_extensions import Self, disjoint_base if sys.version_info < (3, 14): from sqlite3.dbapi2 import version_info as version_info +if sys.version_info >= (3, 15): + from sqlite3.dbapi2 import SQLITE_KEYWORDS as SQLITE_KEYWORDS + if sys.version_info >= (3, 12): from sqlite3.dbapi2 import ( LEGACY_TRANSACTION_CONTROL as LEGACY_TRANSACTION_CONTROL, @@ -211,9 +214,6 @@ if sys.version_info >= (3, 11): if sys.version_info < (3, 12): from sqlite3.dbapi2 import enable_shared_cache as enable_shared_cache, version as version -if sys.version_info < (3, 10): - from sqlite3.dbapi2 import OptimizedUnicode as OptimizedUnicode - _CursorT = TypeVar("_CursorT", bound=Cursor) _SqliteData: TypeAlias = str | ReadableBuffer | int | float | None # Data that is passed through adapters can be of any type accepted by an adapter. @@ -301,6 +301,7 @@ class Connection: def autocommit(self) -> int: ... @autocommit.setter def autocommit(self, val: int) -> None: ... + row_factory: _RowFactoryOptions text_factory: Any if sys.version_info >= (3, 12): @@ -334,7 +335,10 @@ class Connection: def blobopen(self, table: str, column: str, row: int, /, *, readonly: bool = False, name: str = "main") -> Blob: ... def commit(self) -> None: ... - def create_aggregate(self, name: str, n_arg: int, aggregate_class: Callable[[], _AggregateProtocol]) -> None: ... + if sys.version_info >= (3, 15): + def create_aggregate(self, name: str, n_arg: int, aggregate_class: Callable[[], _AggregateProtocol], /) -> None: ... + else: + def create_aggregate(self, name: str, n_arg: int, aggregate_class: Callable[[], _AggregateProtocol]) -> None: ... if sys.version_info >= (3, 11): # num_params determines how many params will be passed to the aggregate class. We provide an overload # for the case where num_params = 1, which is expected to be the common case. @@ -353,13 +357,20 @@ class Connection: ) -> None: ... def create_collation(self, name: str, callback: Callable[[str, str], SupportsIndex] | None, /) -> None: ... - def create_function( - self, name: str, narg: int, func: Callable[..., _SqliteData] | None, *, deterministic: bool = False - ) -> None: ... + if sys.version_info >= (3, 15): + def create_function( + self, name: str, narg: int, func: Callable[..., _SqliteData] | None, /, *, deterministic: bool = False + ) -> None: ... + else: + def create_function( + self, name: str, narg: int, func: Callable[..., _SqliteData] | None, *, deterministic: bool = False + ) -> None: ... + @overload def cursor(self, factory: None = None) -> Cursor: ... @overload def cursor(self, factory: Callable[[Connection], _CursorT]) -> _CursorT: ... + def execute(self, sql: str, parameters: _Parameters = ..., /) -> Cursor: ... def executemany(self, sql: str, parameters: Iterable[_Parameters], /) -> Cursor: ... def executescript(self, sql_script: str, /) -> Cursor: ... @@ -370,11 +381,18 @@ class Connection: def iterdump(self) -> Generator[str]: ... def rollback(self) -> None: ... - def set_authorizer( - self, authorizer_callback: Callable[[int, str | None, str | None, str | None, str | None], int] | None - ) -> None: ... - def set_progress_handler(self, progress_handler: Callable[[], int | None] | None, n: int) -> None: ... - def set_trace_callback(self, trace_callback: Callable[[str], object] | None) -> None: ... + if sys.version_info >= (3, 15): + def set_authorizer( + self, authorizer_callback: Callable[[int, str | None, str | None, str | None, str | None], int] | None, / + ) -> None: ... + def set_progress_handler(self, progress_handler: Callable[[], int | None] | None, /, n: int) -> None: ... + def set_trace_callback(self, trace_callback: Callable[[str], object] | None, /) -> None: ... + else: + def set_authorizer( + self, authorizer_callback: Callable[[int, str | None, str | None, str | None, str | None], int] | None + ) -> None: ... + def set_progress_handler(self, progress_handler: Callable[[], int | None] | None, n: int) -> None: ... + def set_trace_callback(self, trace_callback: Callable[[str], object] | None) -> None: ... # enable_load_extension and load_extension is not available on python distributions compiled # without sqlite3 loadable extension support. see footnotes https://docs.python.org/3/library/sqlite3.html#f1 def enable_load_extension(self, enable: bool, /) -> None: ... @@ -443,10 +461,12 @@ class PrepareProtocol: class Row(Sequence[Any]): def __new__(cls, cursor: Cursor, data: tuple[Any, ...], /) -> Self: ... def keys(self) -> list[str]: ... + @overload # Note: really needs int instead of SupportsIndex def __getitem__(self, key: int | str, /) -> Any: ... @overload # Note: SupportsIndex does work within slices. def __getitem__(self, key: slice[SupportsIndex | None], /) -> tuple[Any, ...]: ... + def __hash__(self) -> int: ... def __iter__(self) -> Iterator[Any]: ... def __len__(self) -> int: ... diff --git a/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi index 9e170a81243d8..0cd676f9bfc87 100644 --- a/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi +++ b/mypy/typeshed/stdlib/sqlite3/dbapi2.pyi @@ -90,6 +90,9 @@ if sys.version_info >= (3, 12): SQLITE_DBCONFIG_WRITABLE_SCHEMA as SQLITE_DBCONFIG_WRITABLE_SCHEMA, ) +if sys.version_info >= (3, 15): + from _sqlite3 import SQLITE_KEYWORDS as SQLITE_KEYWORDS + if sys.version_info >= (3, 11): from _sqlite3 import ( SQLITE_ABORT as SQLITE_ABORT, @@ -215,18 +218,12 @@ if sys.version_info < (3, 14): version: Final[str] if sys.version_info < (3, 12): - if sys.version_info >= (3, 10): - # deprecation wrapper that has a different name for the argument... - @deprecated( - "Deprecated since Python 3.10; removed in Python 3.12. " - "Open database in URI mode using `cache=shared` parameter instead." - ) - def enable_shared_cache(enable: int) -> None: ... - else: - from _sqlite3 import enable_shared_cache as enable_shared_cache - -if sys.version_info < (3, 10): - from _sqlite3 import OptimizedUnicode as OptimizedUnicode + # deprecation wrapper that has a different name for the argument... + @deprecated( + "Deprecated since Python 3.10; removed in Python 3.12. " + "Open database in URI mode using `cache=shared` parameter instead." + ) + def enable_shared_cache(enable: int) -> None: ... paramstyle: Final = "qmark" threadsafety: Literal[0, 1, 3] diff --git a/mypy/typeshed/stdlib/sre_parse.pyi b/mypy/typeshed/stdlib/sre_parse.pyi index eaacbff312a92..6b873f4043b07 100644 --- a/mypy/typeshed/stdlib/sre_parse.pyi +++ b/mypy/typeshed/stdlib/sre_parse.pyi @@ -3,8 +3,7 @@ from collections.abc import Iterable from re import Match, Pattern as _Pattern from sre_constants import * from sre_constants import _NamedIntConstant as _NIC, error as _Error -from typing import Any, Final, overload -from typing_extensions import TypeAlias +from typing import Any, Final, TypeAlias, overload SPECIAL_CHARS: Final = ".\\[{()*+?^$|" REPEAT_CHARS: Final = "*+?{" @@ -91,7 +90,6 @@ if sys.version_info >= (3, 12): def parse_template(source: str, pattern: _Pattern[Any]) -> _TemplateType: ... @overload def parse_template(source: bytes, pattern: _Pattern[Any]) -> _TemplateByteType: ... - else: @overload def parse_template(source: str, state: _Pattern[Any]) -> _TemplateType: ... diff --git a/mypy/typeshed/stdlib/ssl.pyi b/mypy/typeshed/stdlib/ssl.pyi index 57952cf19bbed..590642d99607d 100644 --- a/mypy/typeshed/stdlib/ssl.pyi +++ b/mypy/typeshed/stdlib/ssl.pyi @@ -27,21 +27,21 @@ from _ssl import ( ) from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer from collections.abc import Callable, Iterable -from typing import Any, Final, Literal, NamedTuple, TypedDict, overload, type_check_only -from typing_extensions import Never, Self, TypeAlias, deprecated +from typing import Any, Final, Literal, NamedTuple, TypeAlias, TypedDict, overload, type_check_only +from typing_extensions import Never, Self, deprecated if sys.version_info >= (3, 13): from _ssl import HAS_PSK as HAS_PSK +if sys.version_info >= (3, 15): + from _ssl import HAS_PSK_TLS13 as HAS_PSK_TLS13 + if sys.version_info >= (3, 14): from _ssl import HAS_PHA as HAS_PHA if sys.version_info < (3, 12): from _ssl import RAND_pseudo_bytes as RAND_pseudo_bytes -if sys.version_info < (3, 10): - from _ssl import RAND_egd as RAND_egd - if sys.platform == "win32": from _ssl import enum_certificates as enum_certificates, enum_crls as enum_crls @@ -108,19 +108,16 @@ class VerifyFlags(enum.IntFlag): VERIFY_CRL_CHECK_CHAIN = 0x0C VERIFY_X509_STRICT = 0x20 VERIFY_X509_TRUSTED_FIRST = 0x8000 - if sys.version_info >= (3, 10): - VERIFY_ALLOW_PROXY_CERTS = 0x40 - VERIFY_X509_PARTIAL_CHAIN = 0x80000 + VERIFY_ALLOW_PROXY_CERTS = 0x40 + VERIFY_X509_PARTIAL_CHAIN = 0x80000 VERIFY_DEFAULT: Final = VerifyFlags.VERIFY_DEFAULT VERIFY_CRL_CHECK_LEAF: Final = VerifyFlags.VERIFY_CRL_CHECK_LEAF VERIFY_CRL_CHECK_CHAIN: Final = VerifyFlags.VERIFY_CRL_CHECK_CHAIN VERIFY_X509_STRICT: Final = VerifyFlags.VERIFY_X509_STRICT VERIFY_X509_TRUSTED_FIRST: Final = VerifyFlags.VERIFY_X509_TRUSTED_FIRST - -if sys.version_info >= (3, 10): - VERIFY_ALLOW_PROXY_CERTS: Final = VerifyFlags.VERIFY_ALLOW_PROXY_CERTS - VERIFY_X509_PARTIAL_CHAIN: Final = VerifyFlags.VERIFY_X509_PARTIAL_CHAIN +VERIFY_ALLOW_PROXY_CERTS: Final = VerifyFlags.VERIFY_ALLOW_PROXY_CERTS +VERIFY_X509_PARTIAL_CHAIN: Final = VerifyFlags.VERIFY_X509_PARTIAL_CHAIN class _SSLMethod(enum.IntEnum): PROTOCOL_SSLv23 = 2 @@ -286,33 +283,38 @@ class SSLSocket(socket.socket): ) -> tuple[int, socket._RetAddress]: ... def send(self, data: ReadableBuffer, flags: int = 0) -> int: ... def sendall(self, data: ReadableBuffer, flags: int = 0) -> None: ... + @overload def sendto(self, data: ReadableBuffer, flags_or_addr: socket._Address, addr: None = None) -> int: ... @overload def sendto(self, data: ReadableBuffer, flags_or_addr: int, addr: socket._Address) -> int: ... + def shutdown(self, how: int) -> None: ... @deprecated("Deprecated since Python 3.6. Use `SSLSocket.recv` method instead.") - def read(self, len: int = 1024, buffer: bytearray | None = None) -> bytes: ... + def read(self, len: int = 1024, buffer: WriteableBuffer | None = None) -> bytes: ... @deprecated("Deprecated since Python 3.6. Use `SSLSocket.send` method instead.") def write(self, data: ReadableBuffer) -> int: ... def do_handshake(self, block: bool = False) -> None: ... # block is undocumented + @overload def getpeercert(self, binary_form: Literal[False] = False) -> _PeerCertRetDictType | None: ... @overload def getpeercert(self, binary_form: Literal[True]) -> bytes | None: ... @overload def getpeercert(self, binary_form: bool) -> _PeerCertRetType: ... + def cipher(self) -> tuple[str, str, int] | None: ... def shared_ciphers(self) -> list[tuple[str, str, int]] | None: ... def compression(self) -> str | None: ... + if sys.version_info >= (3, 15): + def group(self) -> str | None: ... + def client_sigalg(self) -> str | None: ... + def server_sigalg(self) -> str | None: ... + def get_channel_binding(self, cb_type: str = "tls-unique") -> bytes | None: ... def selected_alpn_protocol(self) -> str | None: ... - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10. Use ALPN instead.") - def selected_npn_protocol(self) -> str | None: ... - else: - def selected_npn_protocol(self) -> str | None: ... - + @deprecated("Deprecated since Python 3.10. Use ALPN instead.") + def selected_npn_protocol(self) -> str | None: ... def accept(self) -> tuple[SSLSocket, socket._RetAddress]: ... def unwrap(self) -> socket.socket: ... def version(self) -> str | None: ... @@ -346,19 +348,9 @@ if sys.version_info < (3, 12): def cert_time_to_seconds(cert_time: str) -> int: ... def DER_cert_to_PEM_cert(der_cert_bytes: ReadableBuffer) -> str: ... def PEM_cert_to_DER_cert(pem_cert_string: str) -> bytes: ... - -if sys.version_info >= (3, 10): - def get_server_certificate( - addr: tuple[str, int], - ssl_version: int = _SSLMethod.PROTOCOL_TLS_CLIENT, - ca_certs: str | None = None, - timeout: float = ..., - ) -> str: ... - -else: - def get_server_certificate( - addr: tuple[str, int], ssl_version: int = _SSLMethod.PROTOCOL_TLS_CLIENT, ca_certs: str | None = None - ) -> str: ... +def get_server_certificate( + addr: tuple[str, int], ssl_version: int = _SSLMethod.PROTOCOL_TLS_CLIENT, ca_certs: str | None = None, timeout: float = ... +) -> str: ... class TLSVersion(enum.IntEnum): MINIMUM_SUPPORTED = -2 @@ -385,16 +377,13 @@ class SSLContext(_SSLContext): sslsocket_class: type[SSLSocket] keylog_filename: str post_handshake_auth: bool - if sys.version_info >= (3, 10): - security_level: int - if sys.version_info >= (3, 10): - @overload - def __new__(cls, protocol: int, *args: Any, **kwargs: Any) -> Self: ... - @overload - @deprecated("Deprecated since Python 3.10. Use a specific version of the SSL protocol.") - def __new__(cls, protocol: None = None, *args: Any, **kwargs: Any) -> Self: ... - else: - def __new__(cls, protocol: int = ..., *args: Any, **kwargs: Any) -> Self: ... + security_level: int + + @overload + def __new__(cls, protocol: int, *args: Any, **kwargs: Any) -> Self: ... + @overload + @deprecated("Deprecated since Python 3.10. Use a specific version of the SSL protocol.") + def __new__(cls, protocol: None = None, *args: Any, **kwargs: Any) -> Self: ... def load_default_certs(self, purpose: Purpose = Purpose.SERVER_AUTH) -> None: ... def load_verify_locations( @@ -403,22 +392,27 @@ class SSLContext(_SSLContext): capath: StrOrBytesPath | None = None, cadata: str | ReadableBuffer | None = None, ) -> None: ... + @overload def get_ca_certs(self, binary_form: Literal[False] = False) -> list[_PeerCertRetDictType]: ... @overload def get_ca_certs(self, binary_form: Literal[True]) -> list[bytes]: ... @overload def get_ca_certs(self, binary_form: bool = False) -> Any: ... + def get_ciphers(self) -> list[_Cipher]: ... + if sys.version_info >= (3, 15): + def set_ciphersuites(self, ciphersuites: str, /) -> None: ... + def get_groups(self, /, *, include_aliases: bool = False) -> list[str]: ... + def set_groups(self, grouplist: str, /) -> None: ... + def set_client_sigalgs(self, sigalgs: str, /) -> None: ... + def set_server_sigalgs(self, sigalgs: str, /) -> None: ... + def set_default_verify_paths(self) -> None: ... def set_ciphers(self, cipherlist: str, /) -> None: ... def set_alpn_protocols(self, alpn_protocols: Iterable[str]) -> None: ... - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10. Use ALPN instead.") - def set_npn_protocols(self, npn_protocols: Iterable[str]) -> None: ... - else: - def set_npn_protocols(self, npn_protocols: Iterable[str]) -> None: ... - + @deprecated("Deprecated since Python 3.10. Use ALPN instead.") + def set_npn_protocols(self, npn_protocols: Iterable[str]) -> None: ... def set_servername_callback(self, server_name_callback: _SrvnmeCbType | None) -> None: ... def load_dh_params(self, path: str, /) -> None: ... def set_ecdh_curve(self, name: str, /) -> None: ... @@ -447,34 +441,18 @@ def create_default_context( capath: StrOrBytesPath | None = None, cadata: str | ReadableBuffer | None = None, ) -> SSLContext: ... - -if sys.version_info >= (3, 10): - def _create_unverified_context( - protocol: int | None = None, - *, - cert_reqs: int = VerifyMode.CERT_NONE, - check_hostname: bool = False, - purpose: Purpose = Purpose.SERVER_AUTH, - certfile: StrOrBytesPath | None = None, - keyfile: StrOrBytesPath | None = None, - cafile: StrOrBytesPath | None = None, - capath: StrOrBytesPath | None = None, - cadata: str | ReadableBuffer | None = None, - ) -> SSLContext: ... - -else: - def _create_unverified_context( - protocol: int = ..., - *, - cert_reqs: int = VerifyMode.CERT_NONE, - check_hostname: bool = False, - purpose: Purpose = Purpose.SERVER_AUTH, - certfile: StrOrBytesPath | None = None, - keyfile: StrOrBytesPath | None = None, - cafile: StrOrBytesPath | None = None, - capath: StrOrBytesPath | None = None, - cadata: str | ReadableBuffer | None = None, - ) -> SSLContext: ... +def _create_unverified_context( + protocol: int | None = None, + *, + cert_reqs: int = VerifyMode.CERT_NONE, + check_hostname: bool = False, + purpose: Purpose = Purpose.SERVER_AUTH, + certfile: StrOrBytesPath | None = None, + keyfile: StrOrBytesPath | None = None, + cafile: StrOrBytesPath | None = None, + capath: StrOrBytesPath | None = None, + cadata: str | ReadableBuffer | None = None, +) -> SSLContext: ... _create_default_https_context = create_default_context @@ -488,24 +466,27 @@ class SSLObject: @property def session_reused(self) -> bool: ... def __init__(self, *args: Any, **kwargs: Any) -> None: ... - def read(self, len: int = 1024, buffer: bytearray | None = None) -> bytes: ... + def read(self, len: int = 1024, buffer: WriteableBuffer | None = None) -> bytes: ... def write(self, data: ReadableBuffer) -> int: ... + @overload def getpeercert(self, binary_form: Literal[False] = False) -> _PeerCertRetDictType | None: ... @overload def getpeercert(self, binary_form: Literal[True]) -> bytes | None: ... @overload def getpeercert(self, binary_form: bool) -> _PeerCertRetType: ... - def selected_alpn_protocol(self) -> str | None: ... - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10. Use ALPN instead.") - def selected_npn_protocol(self) -> str | None: ... - else: - def selected_npn_protocol(self) -> str | None: ... + def selected_alpn_protocol(self) -> str | None: ... + @deprecated("Deprecated since Python 3.10. Use ALPN instead.") + def selected_npn_protocol(self) -> str | None: ... def cipher(self) -> tuple[str, str, int] | None: ... def shared_ciphers(self) -> list[tuple[str, str, int]] | None: ... def compression(self) -> str | None: ... + if sys.version_info >= (3, 15): + def group(self) -> str | None: ... + def client_sigalg(self) -> str | None: ... + def server_sigalg(self) -> str | None: ... + def pending(self) -> int: ... def do_handshake(self) -> None: ... def unwrap(self) -> None: ... @@ -539,6 +520,9 @@ SSL_ERROR_ZERO_RETURN: Final = SSLErrorNumber.SSL_ERROR_ZERO_RETURN # undocumen def get_protocol_name(protocol_code: int) -> str: ... +if sys.version_info >= (3, 15): + def get_sigalgs() -> list[str]: ... + PEM_FOOTER: Final[str] PEM_HEADER: Final[str] SOCK_STREAM: Final = socket.SOCK_STREAM diff --git a/mypy/typeshed/stdlib/stat.pyi b/mypy/typeshed/stdlib/stat.pyi index 6c26080e06653..155d765d2b160 100644 --- a/mypy/typeshed/stdlib/stat.pyi +++ b/mypy/typeshed/stdlib/stat.pyi @@ -112,3 +112,15 @@ FILE_ATTRIBUTE_VIRTUAL: Final = 65536 if sys.version_info >= (3, 13): # https://github.com/python/cpython/issues/114081#issuecomment-2119017790 SF_RESTRICTED: Final = 0x00080000 + +if sys.version_info >= (3, 15): + STATX_ATTR_COMPRESSED: Final = 0x00000004 + STATX_ATTR_IMMUTABLE: Final = 0x00000010 + STATX_ATTR_APPEND: Final = 0x00000020 + STATX_ATTR_NODUMP: Final = 0x00000040 + STATX_ATTR_ENCRYPTED: Final = 0x00000800 + STATX_ATTR_AUTOMOUNT: Final = 0x00001000 + STATX_ATTR_MOUNT_ROOT: Final = 0x00002000 + STATX_ATTR_VERITY: Final = 0x00100000 + STATX_ATTR_DAX: Final = 0x00200000 + STATX_ATTR_WRITE_ATOMIC: Final = 0x00400000 diff --git a/mypy/typeshed/stdlib/statistics.pyi b/mypy/typeshed/stdlib/statistics.pyi index d9f282b99b662..8cae237f7e0b9 100644 --- a/mypy/typeshed/stdlib/statistics.pyi +++ b/mypy/typeshed/stdlib/statistics.pyi @@ -3,13 +3,16 @@ from _typeshed import SupportsRichComparisonT from collections.abc import Callable, Hashable, Iterable, Sequence, Sized from decimal import Decimal from fractions import Fraction -from typing import Literal, NamedTuple, Protocol, SupportsFloat, SupportsIndex, TypeVar, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Literal, NamedTuple, Protocol, SupportsFloat, SupportsIndex, TypeAlias, TypeVar, type_check_only +from typing_extensions import Self __all__ = [ "StatisticsError", + "covariance", + "correlation", "fmean", "geometric_mean", + "linear_regression", "mean", "harmonic_mean", "pstdev", @@ -26,8 +29,6 @@ __all__ = [ "quantiles", ] -if sys.version_info >= (3, 10): - __all__ += ["covariance", "correlation", "linear_regression"] if sys.version_info >= (3, 13): __all__ += ["kde", "kde_random"] @@ -57,13 +58,7 @@ else: def geometric_mean(data: Iterable[SupportsFloat]) -> float: ... def mean(data: Iterable[_NumberT]) -> _NumberT: ... - -if sys.version_info >= (3, 10): - def harmonic_mean(data: Iterable[_NumberT], weights: Iterable[_Number] | None = None) -> _NumberT: ... - -else: - def harmonic_mean(data: Iterable[_NumberT]) -> _NumberT: ... - +def harmonic_mean(data: Iterable[_NumberT], weights: Iterable[_Number] | None = None) -> _NumberT: ... def median(data: Iterable[_NumberT]) -> _NumberT: ... def median_low(data: Iterable[SupportsRichComparisonT]) -> SupportsRichComparisonT: ... def median_high(data: Iterable[SupportsRichComparisonT]) -> SupportsRichComparisonT: ... @@ -123,22 +118,21 @@ if sys.version_info >= (3, 12): x: Sequence[_Number], y: Sequence[_Number], /, *, method: Literal["linear", "ranked"] = "linear" ) -> float: ... -elif sys.version_info >= (3, 10): +else: def correlation(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ... -if sys.version_info >= (3, 10): - def covariance(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ... +def covariance(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ... - class LinearRegression(NamedTuple): - slope: float - intercept: float +class LinearRegression(NamedTuple): + slope: float + intercept: float if sys.version_info >= (3, 11): def linear_regression( regressor: _SizedIterable[_Number], dependent_variable: _SizedIterable[_Number], /, *, proportional: bool = False ) -> LinearRegression: ... -elif sys.version_info >= (3, 10): +else: def linear_regression( regressor: _SizedIterable[_Number], dependent_variable: _SizedIterable[_Number], / ) -> LinearRegression: ... diff --git a/mypy/typeshed/stdlib/string/__init__.pyi b/mypy/typeshed/stdlib/string/__init__.pyi index c8b32a98e26d7..df70cdc6b21c8 100644 --- a/mypy/typeshed/stdlib/string/__init__.pyi +++ b/mypy/typeshed/stdlib/string/__init__.pyi @@ -54,12 +54,14 @@ class Formatter: def format(self, format_string: LiteralString, /, *args: LiteralString, **kwargs: LiteralString) -> LiteralString: ... @overload def format(self, format_string: str, /, *args: Any, **kwargs: Any) -> str: ... + @overload def vformat( self, format_string: LiteralString, args: Sequence[LiteralString], kwargs: Mapping[LiteralString, LiteralString] ) -> LiteralString: ... @overload def vformat(self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> str: ... + def _vformat( # undocumented self, format_string: str, diff --git a/mypy/typeshed/stdlib/string/templatelib.pyi b/mypy/typeshed/stdlib/string/templatelib.pyi index 9906d31c63915..ee901cdc43b8e 100644 --- a/mypy/typeshed/stdlib/string/templatelib.pyi +++ b/mypy/typeshed/stdlib/string/templatelib.pyi @@ -1,24 +1,24 @@ from collections.abc import Iterator from types import GenericAlias -from typing import Any, Literal, TypeVar, final, overload +from typing import Any, Generic, Literal, TypeVar, final, overload _T = TypeVar("_T") @final class Template: # TODO: consider making `Template` generic on `TypeVarTuple` strings: tuple[str, ...] - interpolations: tuple[Interpolation, ...] + interpolations: tuple[Interpolation[Any], ...] - def __new__(cls, *args: str | Interpolation) -> Template: ... - def __iter__(self) -> Iterator[str | Interpolation]: ... + def __new__(cls, *args: str | Interpolation[Any]) -> Template: ... + def __iter__(self) -> Iterator[str | Interpolation[Any]]: ... def __add__(self, other: Template, /) -> Template: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @property def values(self) -> tuple[Any, ...]: ... # Tuple of interpolation values, which can have any type @final -class Interpolation: - value: Any # TODO: consider making `Interpolation` generic in runtime +class Interpolation(Generic[_T]): + value: _T expression: str conversion: Literal["a", "r", "s"] | None format_spec: str @@ -26,8 +26,8 @@ class Interpolation: __match_args__ = ("value", "expression", "conversion", "format_spec") def __new__( - cls, value: Any, expression: str = "", conversion: Literal["a", "r", "s"] | None = None, format_spec: str = "" - ) -> Interpolation: ... + cls, value: _T, expression: str = "", conversion: Literal["a", "r", "s"] | None = None, format_spec: str = "" + ) -> Interpolation[_T]: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @overload diff --git a/mypy/typeshed/stdlib/subprocess.pyi b/mypy/typeshed/stdlib/subprocess.pyi index f6d7b88193ec3..c191f0e35de9b 100644 --- a/mypy/typeshed/stdlib/subprocess.pyi +++ b/mypy/typeshed/stdlib/subprocess.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import MaybeNone, ReadableBuffer, StrOrBytesPath from collections.abc import Callable, Collection, Iterable, Mapping, Sequence from types import GenericAlias, TracebackType -from typing import IO, Any, AnyStr, Final, Generic, Literal, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing import IO, Any, AnyStr, Final, Generic, Literal, TypeAlias, TypeVar, overload +from typing_extensions import Self __all__ = [ "Popen", @@ -88,7 +88,7 @@ class CompletedProcess(Generic[_T]): if sys.version_info >= (3, 11): # 3.11 adds "process_group" argument - @overload + @overload # text is True def run( args: _CMD, bufsize: int = -1, @@ -101,7 +101,7 @@ if sys.version_info >= (3, 11): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, @@ -122,7 +122,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[str]: ... - @overload + @overload # encoding is str def run( args: _CMD, bufsize: int = -1, @@ -156,7 +156,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[str]: ... - @overload + @overload # errors is str def run( args: _CMD, bufsize: int = -1, @@ -190,7 +190,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[str]: ... - @overload + @overload # universal_newlines is True def run( args: _CMD, bufsize: int = -1, @@ -216,7 +216,7 @@ if sys.version_info >= (3, 11): encoding: str | None = None, errors: str | None = None, input: str | None = None, - text: bool | None = None, + text: Literal[True] | None = None, timeout: float | None = None, user: str | int | None = None, group: str | int | None = None, @@ -225,7 +225,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[str]: ... - @overload + @overload # universal_newlines and text are False, None, or missing def run( args: _CMD, bufsize: int = -1, @@ -259,7 +259,7 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[bytes]: ... - @overload + @overload # fallback def run( args: _CMD, bufsize: int = -1, @@ -293,10 +293,9 @@ if sys.version_info >= (3, 11): pipesize: int = -1, process_group: int | None = None, ) -> CompletedProcess[Any]: ... - -elif sys.version_info >= (3, 10): +else: # 3.10 adds "pipesize" argument - @overload + @overload # text is True def run( args: _CMD, bufsize: int = -1, @@ -309,7 +308,7 @@ elif sys.version_info >= (3, 10): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, @@ -329,7 +328,7 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[str]: ... - @overload + @overload # encoding is str def run( args: _CMD, bufsize: int = -1, @@ -362,7 +361,7 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[str]: ... - @overload + @overload # errors is str def run( args: _CMD, bufsize: int = -1, @@ -395,7 +394,7 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[str]: ... - @overload + @overload # universal_newlines is True def run( args: _CMD, bufsize: int = -1, @@ -421,7 +420,7 @@ elif sys.version_info >= (3, 10): encoding: str | None = None, errors: str | None = None, input: str | None = None, - text: bool | None = None, + text: Literal[True] | None = None, timeout: float | None = None, user: str | int | None = None, group: str | int | None = None, @@ -429,7 +428,7 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[str]: ... - @overload + @overload # universal_newlines and text are False, None, or missing def run( args: _CMD, bufsize: int = -1, @@ -462,7 +461,7 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[bytes]: ... - @overload + @overload # fallback def run( args: _CMD, bufsize: int = -1, @@ -495,458 +494,16 @@ elif sys.version_info >= (3, 10): umask: int = -1, pipesize: int = -1, ) -> CompletedProcess[Any]: ... - -else: - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - capture_output: bool = False, - check: bool = False, - encoding: str | None = None, - errors: str | None = None, - input: str | None = None, - text: Literal[True], - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[str]: ... - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - capture_output: bool = False, - check: bool = False, - encoding: str, - errors: str | None = None, - input: str | None = None, - text: bool | None = None, - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[str]: ... - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - capture_output: bool = False, - check: bool = False, - encoding: str | None = None, - errors: str, - input: str | None = None, - text: bool | None = None, - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[str]: ... - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - *, - universal_newlines: Literal[True], - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - # where the *real* keyword only args start - capture_output: bool = False, - check: bool = False, - encoding: str | None = None, - errors: str | None = None, - input: str | None = None, - text: bool | None = None, - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[str]: ... - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: Literal[False] | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - capture_output: bool = False, - check: bool = False, - encoding: None = None, - errors: None = None, - input: ReadableBuffer | None = None, - text: Literal[False] | None = None, - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[bytes]: ... - @overload - def run( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - capture_output: bool = False, - check: bool = False, - encoding: str | None = None, - errors: str | None = None, - input: _InputString | None = None, - text: bool | None = None, - timeout: float | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> CompletedProcess[Any]: ... - -# Same args as Popen.__init__ -if sys.version_info >= (3, 11): - # 3.11 adds "process_group" argument - def call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - encoding: str | None = None, - timeout: float | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - process_group: int | None = None, - ) -> int: ... - -elif sys.version_info >= (3, 10): - # 3.10 adds "pipesize" argument - def call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - encoding: str | None = None, - timeout: float | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - ) -> int: ... - -else: - def call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - encoding: str | None = None, - timeout: float | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> int: ... - -# Same args as Popen.__init__ -if sys.version_info >= (3, 11): - # 3.11 adds "process_group" argument - def check_call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - timeout: float | None = None, - *, - encoding: str | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - process_group: int | None = None, - ) -> int: ... - -elif sys.version_info >= (3, 10): - # 3.10 adds "pipesize" argument - def check_call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - timeout: float | None = None, - *, - encoding: str | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - ) -> int: ... - -else: - def check_call( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stdout: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - timeout: float | None = None, - *, - encoding: str | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> int: ... - -if sys.version_info >= (3, 11): - # 3.11 adds "process_group" argument - @overload - def check_output( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - timeout: float | None = None, - input: _InputString | None = None, - encoding: str | None = None, - errors: str | None = None, - text: Literal[True], - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - process_group: int | None = None, - ) -> str: ... - @overload - def check_output( - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE = None, - stderr: _FILE = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - timeout: float | None = None, - input: _InputString | None = None, - encoding: str, - errors: str | None = None, - text: bool | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - pipesize: int = -1, - process_group: int | None = None, - ) -> str: ... - @overload - def check_output( + +# Same args as Popen.__init__ +if sys.version_info >= (3, 11): + # 3.11 adds "process_group" argument + def call( args: _CMD, bufsize: int = -1, executable: StrOrBytesPath | None = None, stdin: _FILE = None, + stdout: _FILE = None, stderr: _FILE = None, preexec_fn: Callable[[], object] | None = None, close_fds: bool = True, @@ -960,10 +517,8 @@ if sys.version_info >= (3, 11): start_new_session: bool = False, pass_fds: Collection[int] = (), *, - timeout: float | None = None, - input: _InputString | None = None, encoding: str | None = None, - errors: str, + timeout: float | None = None, text: bool | None = None, user: str | int | None = None, group: str | int | None = None, @@ -971,76 +526,80 @@ if sys.version_info >= (3, 11): umask: int = -1, pipesize: int = -1, process_group: int | None = None, - ) -> str: ... - @overload - def check_output( + ) -> int: ... + +else: + # 3.10 adds "pipesize" argument + def call( args: _CMD, bufsize: int = -1, executable: StrOrBytesPath | None = None, stdin: _FILE = None, + stdout: _FILE = None, stderr: _FILE = None, preexec_fn: Callable[[], object] | None = None, close_fds: bool = True, shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - *, - universal_newlines: Literal[True], + universal_newlines: bool | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, pass_fds: Collection[int] = (), - # where the real keyword only ones start - timeout: float | None = None, - input: _InputString | None = None, + *, encoding: str | None = None, - errors: str | None = None, + timeout: float | None = None, text: bool | None = None, user: str | int | None = None, group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, - process_group: int | None = None, - ) -> str: ... - @overload - def check_output( + ) -> int: ... + +# Same args as Popen.__init__ +if sys.version_info >= (3, 11): + # 3.11 adds "process_group" argument + def check_call( args: _CMD, bufsize: int = -1, executable: StrOrBytesPath | None = None, stdin: _FILE = None, + stdout: _FILE = None, stderr: _FILE = None, preexec_fn: Callable[[], object] | None = None, close_fds: bool = True, shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: Literal[False] | None = None, + universal_newlines: bool | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, pass_fds: Collection[int] = (), - *, timeout: float | None = None, - input: _InputString | None = None, - encoding: None = None, - errors: None = None, - text: Literal[False] | None = None, + *, + encoding: str | None = None, + text: bool | None = None, user: str | int | None = None, group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, process_group: int | None = None, - ) -> bytes: ... - @overload - def check_output( + ) -> int: ... + +else: + # 3.10 adds "pipesize" argument + def check_call( args: _CMD, bufsize: int = -1, executable: StrOrBytesPath | None = None, stdin: _FILE = None, + stdout: _FILE = None, stderr: _FILE = None, preexec_fn: Callable[[], object] | None = None, close_fds: bool = True, @@ -1053,23 +612,20 @@ if sys.version_info >= (3, 11): restore_signals: bool = True, start_new_session: bool = False, pass_fds: Collection[int] = (), - *, timeout: float | None = None, - input: _InputString | None = None, + *, encoding: str | None = None, - errors: str | None = None, text: bool | None = None, user: str | int | None = None, group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, - process_group: int | None = None, - ) -> Any: ... # morally: -> str | bytes + ) -> int: ... -elif sys.version_info >= (3, 10): - # 3.10 adds "pipesize" argument - @overload +if sys.version_info >= (3, 11): + # 3.11 adds "process_group" argument + @overload # text is True def check_output( args: _CMD, bufsize: int = -1, @@ -1081,7 +637,7 @@ elif sys.version_info >= (3, 10): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, @@ -1098,8 +654,9 @@ elif sys.version_info >= (3, 10): extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> str: ... - @overload + @overload # encoding is str def check_output( args: _CMD, bufsize: int = -1, @@ -1128,8 +685,9 @@ elif sys.version_info >= (3, 10): extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> str: ... - @overload + @overload # errors is str def check_output( args: _CMD, bufsize: int = -1, @@ -1158,8 +716,9 @@ elif sys.version_info >= (3, 10): extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> str: ... - @overload + @overload # universal_newlines is True def check_output( args: _CMD, bufsize: int = -1, @@ -1183,14 +742,15 @@ elif sys.version_info >= (3, 10): input: _InputString | None = None, encoding: str | None = None, errors: str | None = None, - text: bool | None = None, + text: Literal[True] | None = None, user: str | int | None = None, group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> str: ... - @overload + @overload # universal_newlines and text are False, None, or missing def check_output( args: _CMD, bufsize: int = -1, @@ -1219,8 +779,9 @@ elif sys.version_info >= (3, 10): extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> bytes: ... - @overload + @overload # fallback def check_output( args: _CMD, bufsize: int = -1, @@ -1249,10 +810,11 @@ elif sys.version_info >= (3, 10): extra_groups: Iterable[str | int] | None = None, umask: int = -1, pipesize: int = -1, + process_group: int | None = None, ) -> Any: ... # morally: -> str | bytes - else: - @overload + # 3.10 adds "pipesize" argument + @overload # text is True def check_output( args: _CMD, bufsize: int = -1, @@ -1264,7 +826,7 @@ else: shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any = None, creationflags: int = 0, restore_signals: bool = True, @@ -1280,8 +842,9 @@ else: group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> str: ... - @overload + @overload # encoding is str def check_output( args: _CMD, bufsize: int = -1, @@ -1309,8 +872,9 @@ else: group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> str: ... - @overload + @overload # errors is str def check_output( args: _CMD, bufsize: int = -1, @@ -1338,8 +902,9 @@ else: group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> str: ... - @overload + @overload # universal_newlines is True def check_output( args: _CMD, bufsize: int = -1, @@ -1363,13 +928,14 @@ else: input: _InputString | None = None, encoding: str | None = None, errors: str | None = None, - text: bool | None = None, + text: Literal[True] | None = None, user: str | int | None = None, group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> str: ... - @overload + @overload # universal_newlines and text are False, None, or missing def check_output( args: _CMD, bufsize: int = -1, @@ -1397,8 +963,9 @@ else: group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> bytes: ... - @overload + @overload # fallback def check_output( args: _CMD, bufsize: int = -1, @@ -1426,6 +993,7 @@ else: group: str | int | None = None, extra_groups: Iterable[str | int] | None = None, umask: int = -1, + pipesize: int = -1, ) -> Any: ... # morally: -> str | bytes PIPE: Final[int] @@ -1462,16 +1030,16 @@ class CalledProcessError(SubprocessError): class Popen(Generic[AnyStr]): args: _CMD - stdin: IO[AnyStr] | None - stdout: IO[AnyStr] | None - stderr: IO[AnyStr] | None + stdin: IO[Any] | None + stdout: IO[Any] | None + stderr: IO[Any] | None pid: int returncode: int | MaybeNone universal_newlines: bool if sys.version_info >= (3, 11): # process_group is added in 3.11 - @overload + @overload # encoding is str def __init__( self: Popen[str], args: _CMD, @@ -1502,7 +1070,7 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - @overload + @overload # errors is str def __init__( self: Popen[str], args: _CMD, @@ -1533,7 +1101,7 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - @overload + @overload # universal_newlines is True def __init__( self: Popen[str], args: _CMD, @@ -1565,7 +1133,7 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - @overload + @overload # text is True def __init__( self: Popen[str], args: _CMD, @@ -1579,7 +1147,7 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, @@ -1596,7 +1164,7 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - @overload + @overload # universal_newlines and text are False, None, or missing def __init__( self: Popen[bytes], args: _CMD, @@ -1627,7 +1195,7 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - @overload + @overload # fallback def __init__( self: Popen[Any], args: _CMD, @@ -1658,9 +1226,9 @@ class Popen(Generic[AnyStr]): pipesize: int = -1, process_group: int | None = None, ) -> None: ... - elif sys.version_info >= (3, 10): + else: # pipesize is added in 3.10 - @overload + @overload # encoding is str def __init__( self: Popen[str], args: _CMD, @@ -1690,7 +1258,7 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - @overload + @overload # errors is str def __init__( self: Popen[str], args: _CMD, @@ -1720,7 +1288,7 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - @overload + @overload # universal_newlines is True def __init__( self: Popen[str], args: _CMD, @@ -1751,7 +1319,7 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - @overload + @overload # text is True def __init__( self: Popen[str], args: _CMD, @@ -1765,7 +1333,7 @@ class Popen(Generic[AnyStr]): shell: bool = False, cwd: StrOrBytesPath | None = None, env: _ENV | None = None, - universal_newlines: bool | None = None, + universal_newlines: Literal[True] | None = None, startupinfo: Any | None = None, creationflags: int = 0, restore_signals: bool = True, @@ -1781,7 +1349,7 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - @overload + @overload # universal_newlines and text are False, None, or missing def __init__( self: Popen[bytes], args: _CMD, @@ -1811,7 +1379,7 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - @overload + @overload # fallback def __init__( self: Popen[Any], args: _CMD, @@ -1841,182 +1409,6 @@ class Popen(Generic[AnyStr]): umask: int = -1, pipesize: int = -1, ) -> None: ... - else: - @overload - def __init__( - self: Popen[str], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - text: bool | None = None, - encoding: str, - errors: str | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... - @overload - def __init__( - self: Popen[str], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - text: bool | None = None, - encoding: str | None = None, - errors: str, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... - @overload - def __init__( - self: Popen[str], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - *, - universal_newlines: Literal[True], - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - # where the *real* keyword only args start - text: bool | None = None, - encoding: str | None = None, - errors: str | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... - @overload - def __init__( - self: Popen[str], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - text: Literal[True], - encoding: str | None = None, - errors: str | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... - @overload - def __init__( - self: Popen[bytes], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: Literal[False] | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - text: Literal[False] | None = None, - encoding: None = None, - errors: None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... - @overload - def __init__( - self: Popen[Any], - args: _CMD, - bufsize: int = -1, - executable: StrOrBytesPath | None = None, - stdin: _FILE | None = None, - stdout: _FILE | None = None, - stderr: _FILE | None = None, - preexec_fn: Callable[[], object] | None = None, - close_fds: bool = True, - shell: bool = False, - cwd: StrOrBytesPath | None = None, - env: _ENV | None = None, - universal_newlines: bool | None = None, - startupinfo: Any | None = None, - creationflags: int = 0, - restore_signals: bool = True, - start_new_session: bool = False, - pass_fds: Collection[int] = (), - *, - text: bool | None = None, - encoding: str | None = None, - errors: str | None = None, - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, - ) -> None: ... def poll(self) -> int | None: ... def wait(self, timeout: float | None = None) -> int: ... diff --git a/mypy/typeshed/stdlib/sunau.pyi b/mypy/typeshed/stdlib/sunau.pyi index f83a0a4c520e7..1f18c041465ef 100644 --- a/mypy/typeshed/stdlib/sunau.pyi +++ b/mypy/typeshed/stdlib/sunau.pyi @@ -1,6 +1,6 @@ from _typeshed import Unused -from typing import IO, Any, Final, Literal, NamedTuple, NoReturn, overload -from typing_extensions import Self, TypeAlias +from typing import IO, Any, Final, Literal, NamedTuple, NoReturn, TypeAlias, overload +from typing_extensions import Self _File: TypeAlias = str | IO[bytes] diff --git a/mypy/typeshed/stdlib/symbol.pyi b/mypy/typeshed/stdlib/symbol.pyi deleted file mode 100644 index 5344ce504c6c7..0000000000000 --- a/mypy/typeshed/stdlib/symbol.pyi +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Final - -single_input: Final[int] -file_input: Final[int] -eval_input: Final[int] -decorator: Final[int] -decorators: Final[int] -decorated: Final[int] -async_funcdef: Final[int] -funcdef: Final[int] -parameters: Final[int] -typedargslist: Final[int] -tfpdef: Final[int] -varargslist: Final[int] -vfpdef: Final[int] -stmt: Final[int] -simple_stmt: Final[int] -small_stmt: Final[int] -expr_stmt: Final[int] -annassign: Final[int] -testlist_star_expr: Final[int] -augassign: Final[int] -del_stmt: Final[int] -pass_stmt: Final[int] -flow_stmt: Final[int] -break_stmt: Final[int] -continue_stmt: Final[int] -return_stmt: Final[int] -yield_stmt: Final[int] -raise_stmt: Final[int] -import_stmt: Final[int] -import_name: Final[int] -import_from: Final[int] -import_as_name: Final[int] -dotted_as_name: Final[int] -import_as_names: Final[int] -dotted_as_names: Final[int] -dotted_name: Final[int] -global_stmt: Final[int] -nonlocal_stmt: Final[int] -assert_stmt: Final[int] -compound_stmt: Final[int] -async_stmt: Final[int] -if_stmt: Final[int] -while_stmt: Final[int] -for_stmt: Final[int] -try_stmt: Final[int] -with_stmt: Final[int] -with_item: Final[int] -except_clause: Final[int] -suite: Final[int] -test: Final[int] -test_nocond: Final[int] -lambdef: Final[int] -lambdef_nocond: Final[int] -or_test: Final[int] -and_test: Final[int] -not_test: Final[int] -comparison: Final[int] -comp_op: Final[int] -star_expr: Final[int] -expr: Final[int] -xor_expr: Final[int] -and_expr: Final[int] -shift_expr: Final[int] -arith_expr: Final[int] -term: Final[int] -factor: Final[int] -power: Final[int] -atom_expr: Final[int] -atom: Final[int] -testlist_comp: Final[int] -trailer: Final[int] -subscriptlist: Final[int] -subscript: Final[int] -sliceop: Final[int] -exprlist: Final[int] -testlist: Final[int] -dictorsetmaker: Final[int] -classdef: Final[int] -arglist: Final[int] -argument: Final[int] -comp_iter: Final[int] -comp_for: Final[int] -comp_if: Final[int] -encoding_decl: Final[int] -yield_expr: Final[int] -yield_arg: Final[int] -sync_comp_for: Final[int] -func_body_suite: Final[int] -func_type: Final[int] -func_type_input: Final[int] -namedexpr_test: Final[int] -typelist: Final[int] -sym_name: Final[dict[int, str]] diff --git a/mypy/typeshed/stdlib/symtable.pyi b/mypy/typeshed/stdlib/symtable.pyi index a727b878688ed..d41ef8419dec8 100644 --- a/mypy/typeshed/stdlib/symtable.pyi +++ b/mypy/typeshed/stdlib/symtable.pyi @@ -9,7 +9,11 @@ __all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"] if sys.version_info >= (3, 13): __all__ += ["SymbolTableType"] -def symtable(code: str, filename: str, compile_type: str) -> SymbolTable: ... +if sys.version_info >= (3, 15): + def symtable(code: str, filename: str, compile_type: str, *, module: str | None = None) -> SymbolTable: ... + +else: + def symtable(code: str, filename: str, compile_type: str) -> SymbolTable: ... if sys.version_info >= (3, 13): from enum import StrEnum @@ -46,14 +50,14 @@ class Function(SymbolTable): def get_locals(self) -> tuple[str, ...]: ... def get_globals(self) -> tuple[str, ...]: ... def get_frees(self) -> tuple[str, ...]: ... + if sys.version_info >= (3, 15): + def get_cells(self) -> tuple[str, ...]: ... + def get_nonlocals(self) -> tuple[str, ...]: ... class Class(SymbolTable): - if sys.version_info >= (3, 14): - @deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") - def get_methods(self) -> tuple[str, ...]: ... - else: - def get_methods(self) -> tuple[str, ...]: ... + @deprecated("Deprecated since Python 3.14; will be removed in Python 3.16.") + def get_methods(self) -> tuple[str, ...]: ... class Symbol: def __init__( @@ -79,6 +83,8 @@ class Symbol: if sys.version_info >= (3, 14): def is_comp_iter(self) -> bool: ... def is_comp_cell(self) -> bool: ... + if sys.version_info >= (3, 15): + def is_cell(self) -> bool: ... def is_namespace(self) -> bool: ... def get_namespaces(self) -> Sequence[SymbolTable]: ... diff --git a/mypy/typeshed/stdlib/sys/__init__.pyi b/mypy/typeshed/stdlib/sys/__init__.pyi index 6abef85dfb7f1..72e894b9305fd 100644 --- a/mypy/typeshed/stdlib/sys/__init__.pyi +++ b/mypy/typeshed/stdlib/sys/__init__.pyi @@ -4,18 +4,30 @@ from _typeshed.importlib import MetaPathFinderProtocol, PathEntryFinderProtocol from builtins import object as _object from collections.abc import AsyncGenerator, Callable, Sequence from io import TextIOWrapper -from types import FrameType, ModuleType, TracebackType -from typing import Any, Final, Literal, NoReturn, Protocol, TextIO, TypeVar, final, overload, type_check_only -from typing_extensions import LiteralString, TypeAlias, deprecated +from types import FrameType, ModuleType, SimpleNamespace, TracebackType +from typing import Any, Final, Literal, NoReturn, Protocol, TextIO, TypeAlias, TypeVar, final, overload, type_check_only +from typing_extensions import LiteralString, deprecated _T = TypeVar("_T") +_LazyImportMode: TypeAlias = Literal["normal", "all", "none"] +_LazyImportFilter: TypeAlias = Callable[[str | None, str, tuple[str, ...] | None], bool] # see https://github.com/python/typeshed/issues/8513#issue-1333671093 for the rationale behind this alias _ExitCode: TypeAlias = str | int | None +if sys.version_info >= (3, 15): + @type_check_only + class _AbiInfo(SimpleNamespace): + pointer_bits: int + free_threaded: bool + debug: bool + byteorder: Literal["little", "big"] + # ----- sys variables ----- if sys.platform != "win32": abiflags: str +if sys.version_info >= (3, 15): + abi_info: _AbiInfo argv: list[str] base_exec_prefix: str base_prefix: str @@ -40,8 +52,9 @@ maxsize: int maxunicode: int meta_path: list[MetaPathFinderProtocol] modules: dict[str, ModuleType] -if sys.version_info >= (3, 10): - orig_argv: list[str] +if sys.version_info >= (3, 15): + lazy_modules: set[str] +orig_argv: list[str] path: list[str] path_hooks: list[Callable[[str], PathEntryFinderProtocol]] path_importer_cache: dict[str, PathEntryFinderProtocol | None] @@ -65,9 +78,7 @@ ps2: object stdin: TextIO | MaybeNone stdout: TextIO | MaybeNone stderr: TextIO | MaybeNone - -if sys.version_info >= (3, 10): - stdlib_module_names: frozenset[str] +stdlib_module_names: frozenset[str] __stdin__: Final[TextIOWrapper | None] # Contains the original value of stdin __stdout__: Final[TextIOWrapper | None] # Contains the original value of stdout @@ -124,7 +135,7 @@ class _flags(_UninstantiableStructseq, tuple[int, ...]): "safe_path", "int_max_str_digits", ) - elif sys.version_info >= (3, 10): + else: __match_args__: Final = ( "debug", "inspect", @@ -175,9 +186,8 @@ class _flags(_UninstantiableStructseq, tuple[int, ...]): def dev_mode(self) -> bool: ... @property def utf8_mode(self) -> int: ... - if sys.version_info >= (3, 10): - @property - def warn_default_encoding(self) -> int: ... + @property + def warn_default_encoding(self) -> int: ... if sys.version_info >= (3, 11): @property def safe_path(self) -> bool: ... @@ -203,20 +213,19 @@ float_info: _float_info @final @type_check_only class _float_info(structseq[float], tuple[float, int, int, float, int, int, int, int, float, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ( - "max", - "max_exp", - "max_10_exp", - "min", - "min_exp", - "min_10_exp", - "dig", - "mant_dig", - "epsilon", - "radix", - "rounds", - ) + __match_args__: Final = ( + "max", + "max_exp", + "max_10_exp", + "min", + "min_exp", + "min_10_exp", + "dig", + "mant_dig", + "epsilon", + "radix", + "rounds", + ) @property def max(self) -> float: ... # DBL_MAX @@ -247,8 +256,7 @@ hash_info: _hash_info @final @type_check_only class _hash_info(structseq[Any | int], tuple[int, int, int, int, int, str, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("width", "modulus", "inf", "nan", "imag", "algorithm", "hash_bits", "seed_bits", "cutoff") + __match_args__: Final = ("width", "modulus", "inf", "nan", "imag", "algorithm", "hash_bits", "seed_bits", "cutoff") @property def width(self) -> int: ... @@ -290,8 +298,7 @@ int_info: _int_info @final @type_check_only class _int_info(structseq[int], tuple[int, int, int, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("bits_per_digit", "sizeof_digit", "default_max_str_digits", "str_digits_check_threshold") + __match_args__: Final = ("bits_per_digit", "sizeof_digit", "default_max_str_digits", "str_digits_check_threshold") @property def bits_per_digit(self) -> int: ... @@ -309,8 +316,7 @@ _ThreadInfoLock: TypeAlias = Literal["semaphore", "mutex+cond"] | None @final @type_check_only class _thread_info(_UninstantiableStructseq, tuple[_ThreadInfoName, _ThreadInfoLock, str | None]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("name", "lock", "version") + __match_args__: Final = ("name", "lock", "version") @property def name(self) -> _ThreadInfoName: ... @@ -326,8 +332,7 @@ _ReleaseLevel: TypeAlias = Literal["alpha", "beta", "candidate", "final"] @final @type_check_only class _version_info(_UninstantiableStructseq, tuple[int, int, int, _ReleaseLevel, int]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("major", "minor", "micro", "releaselevel", "serial") + __match_args__: Final = ("major", "minor", "micro", "releaselevel", "serial") @property def major(self) -> int: ... @@ -385,6 +390,11 @@ if sys.platform != "win32": def getfilesystemencoding() -> LiteralString: ... def getfilesystemencodeerrors() -> LiteralString: ... + +if sys.version_info >= (3, 15): + def get_lazy_imports() -> _LazyImportMode: ... + def get_lazy_imports_filter() -> _LazyImportFilter | None: ... + def getrefcount(object: Any, /) -> int: ... def getrecursionlimit() -> int: ... def getsizeof(obj: object, default: int = ...) -> int: ... @@ -467,8 +477,7 @@ _AsyncgenHook: TypeAlias = Callable[[AsyncGenerator[Any, Any]], None] | None @final @type_check_only class _asyncgen_hooks(structseq[_AsyncgenHook], tuple[_AsyncgenHook, _AsyncgenHook]): - if sys.version_info >= (3, 10): - __match_args__: Final = ("firstiter", "finalizer") + __match_args__: Final = ("firstiter", "finalizer") @property def firstiter(self) -> _AsyncgenHook: ... @@ -496,6 +505,10 @@ def set_coroutine_origin_tracking_depth(depth: int) -> None: ... def set_int_max_str_digits(maxdigits: int) -> None: ... def get_int_max_str_digits() -> int: ... +if sys.version_info >= (3, 15): + def set_lazy_imports(mode: _LazyImportMode) -> None: ... + def set_lazy_imports_filter(filter: _LazyImportFilter | None) -> None: ... + if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): def getunicodeinternedsize(*, _only_immortal: bool = False) -> int: ... @@ -518,3 +531,7 @@ if sys.version_info >= (3, 14): def is_remote_debug_enabled() -> bool: ... def remote_exec(pid: int, script: StrOrBytesPath) -> None: ... def _is_immortal(op: object, /) -> bool: ... + + from . import __jit + + _jit = __jit diff --git a/mypy/typeshed/stdlib/sys/__jit.pyi b/mypy/typeshed/stdlib/sys/__jit.pyi new file mode 100644 index 0000000000000..90fb65c1d9efe --- /dev/null +++ b/mypy/typeshed/stdlib/sys/__jit.pyi @@ -0,0 +1,11 @@ +# This py314+ module provides annotations for `sys._jit`. +# It's named `sys.__jit` in typeshed, +# because trying to import `sys._jit` will fail at runtime! +# At runtime, `sys._jit` has the unusual status +# of being a `types.ModuleType` instance that cannot be directly imported, +# (same as sys.monitoring) +# and exists in the `sys`-module namespace despite `sys` not being a package. + +def is_available() -> bool: ... +def is_enabled() -> bool: ... +def is_active() -> bool: ... diff --git a/mypy/typeshed/stdlib/sys/_monitoring.pyi b/mypy/typeshed/stdlib/sys/_monitoring.pyi index 83f1e7dd0f1df..b999d63f64709 100644 --- a/mypy/typeshed/stdlib/sys/_monitoring.pyi +++ b/mypy/typeshed/stdlib/sys/_monitoring.pyi @@ -1,8 +1,9 @@ # This py312+ module provides annotations for `sys.monitoring`. # It's named `sys._monitoring` in typeshed, # because trying to import `sys.monitoring` will fail at runtime! -# At runtime, `sys.monitoring` has the unique status +# At runtime, `sys.monitoring` has the unusual status # of being a `types.ModuleType` instance that cannot be directly imported, +# (same as sys._jit) # and exists in the `sys`-module namespace despite `sys` not being a package. import sys diff --git a/mypy/typeshed/stdlib/sysconfig.pyi b/mypy/typeshed/stdlib/sysconfig.pyi index 8de7ddc4255f2..cfa3fbaceb731 100644 --- a/mypy/typeshed/stdlib/sysconfig.pyi +++ b/mypy/typeshed/stdlib/sysconfig.pyi @@ -21,35 +21,37 @@ __all__ = [ def get_config_var(name: Literal["SO"]) -> Any: ... @overload def get_config_var(name: str) -> Any: ... + @overload def get_config_vars() -> dict[str, Any]: ... @overload def get_config_vars(arg: str, /, *args: str) -> list[Any]: ... -def get_scheme_names() -> tuple[str, ...]: ... -if sys.version_info >= (3, 10): - def get_default_scheme() -> LiteralString: ... - def get_preferred_scheme(key: Literal["prefix", "home", "user"]) -> LiteralString: ... - # Documented -- see https://docs.python.org/3/library/sysconfig.html#sysconfig._get_preferred_schemes - def _get_preferred_schemes() -> dict[Literal["prefix", "home", "user"], LiteralString]: ... +def get_scheme_names() -> tuple[str, ...]: ... +def get_default_scheme() -> LiteralString: ... +def get_preferred_scheme(key: Literal["prefix", "home", "user"]) -> LiteralString: ... +# Documented -- see https://docs.python.org/3/library/sysconfig.html#sysconfig._get_preferred_schemes +def _get_preferred_schemes() -> dict[Literal["prefix", "home", "user"], LiteralString]: ... def get_path_names() -> tuple[str, ...]: ... def get_path(name: str, scheme: str = ..., vars: dict[str, Any] | None = None, expand: bool = True) -> str: ... def get_paths(scheme: str = ..., vars: dict[str, Any] | None = None, expand: bool = True) -> dict[str, str]: ... def get_python_version() -> str: ... def get_platform() -> str: ... -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 15): + def is_python_build() -> bool: ... +elif sys.version_info >= (3, 11): @overload def is_python_build() -> bool: ... @overload @deprecated("The `check_home` parameter is deprecated since Python 3.12; removed in Python 3.15.") def is_python_build(check_home: object = None) -> bool: ... - -elif sys.version_info >= (3, 11): - def is_python_build(check_home: object = None) -> bool: ... - else: + @overload + def is_python_build() -> bool: ... + @overload + @deprecated("The `check_home` parameter is deprecated since Python 3.12; removed in Python 3.15.") def is_python_build(check_home: bool = False) -> bool: ... def parse_config_h(fp: IO[Any], vars: dict[str, Any] | None = None) -> dict[str, Any]: ... diff --git a/mypy/typeshed/stdlib/syslog.pyi b/mypy/typeshed/stdlib/syslog.pyi index b639c28c7f2d7..0cb97dcd3ca7e 100644 --- a/mypy/typeshed/stdlib/syslog.pyi +++ b/mypy/typeshed/stdlib/syslog.pyi @@ -51,6 +51,7 @@ if sys.platform != "win32": def closelog() -> None: ... def openlog(ident: str = ..., logoption: int = 0, facility: int = ...) -> None: ... def setlogmask(maskpri: int, /) -> int: ... + @overload def syslog(priority: int, message: str) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/tarfile.pyi b/mypy/typeshed/stdlib/tarfile.pyi index 7d6bb341db314..6c55683fbaa37 100644 --- a/mypy/typeshed/stdlib/tarfile.pyi +++ b/mypy/typeshed/stdlib/tarfile.pyi @@ -6,8 +6,8 @@ from builtins import list as _list # aliases to avoid name clashes with fields from collections.abc import Callable, Iterable, Iterator, Mapping from gzip import _ReadableFileobj as _GzipReadableFileobj, _WritableFileobj as _GzipWritableFileobj from types import TracebackType -from typing import IO, ClassVar, Final, Literal, Protocol, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated +from typing import IO, ClassVar, Final, Literal, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self, deprecated if sys.version_info >= (3, 14): from compression.zstd import ZstdDict @@ -134,6 +134,26 @@ class TarFile: extraction_filter: _FilterFunction | None if sys.version_info >= (3, 13): stream: bool + if sys.version_info >= (3, 15): + def __init__( + self, + name: StrOrBytesPath | None = None, + mode: Literal["r", "a", "w", "x"] = "r", + fileobj: _Fileobj | None = None, + format: int | None = None, + tarinfo: type[TarInfo] | None = None, + dereference: bool | None = None, + ignore_zeros: bool | None = None, + encoding: str | None = None, + errors: str = "surrogateescape", + pax_headers: Mapping[str, str] | None = None, + debug: Literal[0, 1, 2, 3] | None = None, # default 0 + errorlevel: Literal[0, 1, 2] | None = None, # default 1 + copybufsize: int | None = None, # undocumented + stream: bool = False, + mtime: float | None = None, + ) -> None: ... + elif sys.version_info >= (3, 13): def __init__( self, name: StrOrBytesPath | None = None, @@ -193,6 +213,7 @@ class TarFile: debug: Literal[0, 1, 2, 3] | None = None, # default 0 errorlevel: Literal[0, 1, 2] | None = None, # default 1 ) -> Self: ... + if sys.version_info >= (3, 14): @overload @classmethod @@ -495,6 +516,7 @@ class TarFile: errorlevel: Literal[0, 1, 2] | None = None, # default 1 compresslevel: int = 9, ) -> Self: ... + @classmethod def taropen( cls, @@ -512,6 +534,7 @@ class TarFile: debug: Literal[0, 1, 2, 3] | None = None, # default 0 errorlevel: Literal[0, 1, 2] | None = None, # default 1 ) -> Self: ... + @overload @classmethod def gzopen( @@ -548,6 +571,7 @@ class TarFile: debug: Literal[0, 1, 2, 3] | None = None, # default 0 errorlevel: Literal[0, 1, 2] | None = None, # default 1 ) -> Self: ... + @overload @classmethod def bz2open( @@ -584,6 +608,7 @@ class TarFile: debug: Literal[0, 1, 2, 3] | None = None, # default 0 errorlevel: Literal[0, 1, 2] | None = None, # default 1 ) -> Self: ... + @classmethod def xzopen( cls, @@ -785,24 +810,24 @@ class TarInfo: gname: str pax_headers: Mapping[str, str] def __init__(self, name: str = "") -> None: ... - if sys.version_info >= (3, 13): - @property - @deprecated("Deprecated since Python 3.13; will be removed in Python 3.16.") - def tarfile(self) -> TarFile | None: ... - @tarfile.setter - @deprecated("Deprecated since Python 3.13; will be removed in Python 3.16.") - def tarfile(self, tarfile: TarFile | None) -> None: ... - else: - tarfile: TarFile | None + + @property + @deprecated("Deprecated since Python 3.13; will be removed in Python 3.16.") + def tarfile(self) -> TarFile | None: ... + @tarfile.setter + @deprecated("Deprecated since Python 3.13; will be removed in Python 3.16.") + def tarfile(self, tarfile: TarFile | None) -> None: ... @classmethod def frombuf(cls, buf: bytes | bytearray, encoding: str, errors: str) -> Self: ... @classmethod def fromtarfile(cls, tarfile: TarFile) -> Self: ... + @property def linkpath(self) -> str: ... @linkpath.setter def linkpath(self, linkname: str) -> None: ... + def replace( self, *, diff --git a/mypy/typeshed/stdlib/tempfile.pyi b/mypy/typeshed/stdlib/tempfile.pyi index 26491074ff71d..1df04a3d3667a 100644 --- a/mypy/typeshed/stdlib/tempfile.pyi +++ b/mypy/typeshed/stdlib/tempfile.pyi @@ -81,7 +81,6 @@ if sys.version_info >= (3, 12): errors: str | None = None, delete_on_close: bool = True, ) -> _TemporaryFileWrapper[Any]: ... - else: @overload def NamedTemporaryFile( @@ -249,18 +248,21 @@ class _TemporaryFileWrapper(IO[AnyStr]): def tell(self) -> int: ... def truncate(self, size: int | None = ...) -> int: ... def writable(self) -> bool: ... + @overload def write(self: _TemporaryFileWrapper[str], s: str, /) -> int: ... @overload def write(self: _TemporaryFileWrapper[bytes], s: ReadableBuffer, /) -> int: ... @overload def write(self, s: AnyStr, /) -> int: ... + @overload def writelines(self: _TemporaryFileWrapper[str], lines: Iterable[str]) -> None: ... @overload def writelines(self: _TemporaryFileWrapper[bytes], lines: Iterable[ReadableBuffer]) -> None: ... @overload def writelines(self, lines: Iterable[AnyStr]) -> None: ... + @property def closed(self) -> bool: ... @@ -277,6 +279,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def encoding(self) -> str: ... # undocumented @property def newlines(self) -> str | tuple[str, ...] | None: ... # undocumented + # bytes needs to go first, as default mode is to open as bytes @overload def __init__( @@ -348,6 +351,7 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): dir: str | None = None, errors: str | None = None, ) -> None: ... + @property def errors(self) -> str | None: ... def rollover(self) -> None: ... @@ -384,12 +388,14 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def write(self: SpooledTemporaryFile[bytes], s: ReadableBuffer) -> int: ... @overload def write(self, s: AnyStr) -> int: ... + @overload # type: ignore[override] def writelines(self: SpooledTemporaryFile[str], iterable: Iterable[str]) -> None: ... @overload def writelines(self: SpooledTemporaryFile[bytes], iterable: Iterable[ReadableBuffer]) -> None: ... @overload def writelines(self, iterable: Iterable[AnyStr]) -> None: ... + def __iter__(self) -> Iterator[AnyStr]: ... # type: ignore[override] # These exist at runtime only on 3.11+. def readable(self) -> bool: ... @@ -421,7 +427,7 @@ class TemporaryDirectory(Generic[AnyStr]): *, delete: bool = True, ) -> None: ... - elif sys.version_info >= (3, 10): + else: @overload def __init__( self: TemporaryDirectory[str], @@ -438,18 +444,6 @@ class TemporaryDirectory(Generic[AnyStr]): dir: BytesPath | None = None, ignore_cleanup_errors: bool = False, ) -> None: ... - else: - @overload - def __init__( - self: TemporaryDirectory[str], suffix: str | None = None, prefix: str | None = None, dir: StrPath | None = None - ) -> None: ... - @overload - def __init__( - self: TemporaryDirectory[bytes], - suffix: bytes | None = None, - prefix: bytes | None = None, - dir: BytesPath | None = None, - ) -> None: ... def cleanup(self) -> None: ... def __enter__(self) -> AnyStr: ... @@ -471,6 +465,7 @@ def mkstemp( def mkdtemp(suffix: str | None = None, prefix: str | None = None, dir: StrPath | None = None) -> str: ... @overload def mkdtemp(suffix: bytes | None = None, prefix: bytes | None = None, dir: BytesPath | None = None) -> bytes: ... + @deprecated("Deprecated since Python 2.3. Use `mkstemp()` or `NamedTemporaryFile(delete=False)` instead.") def mktemp(suffix: str = "", prefix: str = "tmp", dir: StrPath | None = None) -> str: ... def gettempdirb() -> bytes: ... diff --git a/mypy/typeshed/stdlib/termios.pyi b/mypy/typeshed/stdlib/termios.pyi index a35be5dfe740a..971c9ad46bf45 100644 --- a/mypy/typeshed/stdlib/termios.pyi +++ b/mypy/typeshed/stdlib/termios.pyi @@ -1,7 +1,6 @@ import sys from _typeshed import FileDescriptorLike -from typing import Any, Final -from typing_extensions import TypeAlias +from typing import Any, Final, TypeAlias # Must be a list of length 7, containing 6 ints and a list of NCCS 1-character bytes or ints. _Attr: TypeAlias = list[int | list[bytes | int]] | list[int | list[bytes]] | list[int | list[int]] diff --git a/mypy/typeshed/stdlib/threading.pyi b/mypy/typeshed/stdlib/threading.pyi index 03c8865d3c0a4..6b51b424cee6d 100644 --- a/mypy/typeshed/stdlib/threading.pyi +++ b/mypy/typeshed/stdlib/threading.pyi @@ -2,11 +2,11 @@ import _thread import sys from _thread import _ExceptHookArgs, get_native_id as get_native_id from _typeshed import ProfileFunction, TraceFunction -from collections.abc import Callable, Iterable, Mapping +from collections.abc import Callable, Iterable, Iterator, Mapping from contextvars import Context from types import TracebackType from typing import Any, Final, TypeVar, final -from typing_extensions import deprecated +from typing_extensions import Self, deprecated _T = TypeVar("_T") @@ -29,6 +29,8 @@ __all__ = [ "Timer", "ThreadError", "ExceptHookArgs", + "getprofile", + "gettrace", "setprofile", "settrace", "local", @@ -37,12 +39,12 @@ __all__ = [ "get_native_id", ] -if sys.version_info >= (3, 10): - __all__ += ["getprofile", "gettrace"] - if sys.version_info >= (3, 12): __all__ += ["setprofile_all_threads", "settrace_all_threads"] +if sys.version_info >= (3, 15): + __all__ += ["concurrent_tee", "serialize_iterator", "synchronized_iterator"] + _profile_hook: ProfileFunction | None def active_count() -> int: ... @@ -61,9 +63,21 @@ if sys.version_info >= (3, 12): def setprofile_all_threads(func: ProfileFunction | None) -> None: ... def settrace_all_threads(func: TraceFunction | None) -> None: ... -if sys.version_info >= (3, 10): - def gettrace() -> TraceFunction | None: ... - def getprofile() -> ProfileFunction | None: ... +def gettrace() -> TraceFunction | None: ... +def getprofile() -> ProfileFunction | None: ... + +if sys.version_info >= (3, 15): + @final + class serialize_iterator(Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _T: ... + def send(self, value: Any, /) -> _T: ... + def throw(self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ...) -> _T: ... + def close(self) -> None: ... + + def synchronized_iterator(func: Callable[..., Iterable[_T]]) -> Callable[..., Iterator[_T]]: ... + def concurrent_tee(iterable: Iterable[_T], n: int = 2) -> tuple[Iterator[_T], ...]: ... def stack_size(size: int = 0, /) -> int: ... @@ -173,8 +187,7 @@ class Event: def wait(self, timeout: float | None = None) -> bool: ... excepthook: Callable[[_ExceptHookArgs], object] -if sys.version_info >= (3, 10): - __excepthook__: Callable[[_ExceptHookArgs], object] +__excepthook__: Callable[[_ExceptHookArgs], object] ExceptHookArgs = _ExceptHookArgs class Timer(Thread): diff --git a/mypy/typeshed/stdlib/time.pyi b/mypy/typeshed/stdlib/time.pyi index d0853792b636d..9b5344b326cec 100644 --- a/mypy/typeshed/stdlib/time.pyi +++ b/mypy/typeshed/stdlib/time.pyi @@ -1,7 +1,6 @@ import sys from _typeshed import structseq -from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, Union, final, type_check_only -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, Protocol, SupportsFloat, SupportsIndex, TypeAlias, Union, final, type_check_only _TimeTuple: TypeAlias = tuple[int, int, int, int, int, int, int, int, int] @@ -47,8 +46,7 @@ if sys.platform == "linux": # https://github.com/python/typeshed/pull/6560#discussion_r767162532 @final class struct_time(structseq[Any | int], _TimeTuple): - if sys.version_info >= (3, 10): - __match_args__: Final = ("tm_year", "tm_mon", "tm_mday", "tm_hour", "tm_min", "tm_sec", "tm_wday", "tm_yday", "tm_isdst") + __match_args__: Final = ("tm_year", "tm_mon", "tm_mday", "tm_hour", "tm_min", "tm_sec", "tm_wday", "tm_yday", "tm_isdst") @property def tm_year(self) -> int: ... diff --git a/mypy/typeshed/stdlib/timeit.pyi b/mypy/typeshed/stdlib/timeit.pyi index 61935815f68b2..cc2b2027a8201 100644 --- a/mypy/typeshed/stdlib/timeit.pyi +++ b/mypy/typeshed/stdlib/timeit.pyi @@ -1,7 +1,7 @@ +import sys import time from collections.abc import Callable, Sequence -from typing import IO, Any -from typing_extensions import TypeAlias +from typing import IO, Any, TypeAlias __all__ = ["Timer", "timeit", "repeat", "default_timer"] @@ -21,7 +21,12 @@ class Timer: def print_exc(self, file: IO[str] | None = None) -> None: ... def timeit(self, number: int = 1000000) -> float: ... def repeat(self, repeat: int = 5, number: int = 1000000) -> list[float]: ... - def autorange(self, callback: Callable[[int, float], object] | None = None) -> tuple[int, float]: ... + if sys.version_info >= (3, 15): + def autorange( + self, callback: Callable[[int, float], object] | None = None, target_time: float = 0.2 + ) -> tuple[int, float]: ... + else: + def autorange(self, callback: Callable[[int, float], object] | None = None) -> tuple[int, float]: ... def timeit( stmt: _Stmt = "pass", diff --git a/mypy/typeshed/stdlib/tkinter/__init__.pyi b/mypy/typeshed/stdlib/tkinter/__init__.pyi index a70ef2351f3d4..94787911df8d4 100644 --- a/mypy/typeshed/stdlib/tkinter/__init__.pyi +++ b/mypy/typeshed/stdlib/tkinter/__init__.pyi @@ -5,8 +5,22 @@ from collections.abc import Callable, Iterable, Mapping, Sequence from tkinter.constants import * from tkinter.font import _FontDescription from types import GenericAlias, TracebackType -from typing import Any, ClassVar, Final, Generic, Literal, NamedTuple, Protocol, TypedDict, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias, TypeVarTuple, Unpack, deprecated, disjoint_base +from typing import ( + Any, + ClassVar, + Final, + Generic, + Literal, + NamedTuple, + ParamSpec, + Protocol, + TypeAlias, + TypedDict, + TypeVar, + overload, + type_check_only, +) +from typing_extensions import TypeVarTuple, Unpack, deprecated, disjoint_base if sys.version_info >= (3, 11): from enum import StrEnum @@ -299,6 +313,9 @@ class Event(Generic[_W_co]): type: EventType widget: _W_co delta: int + if sys.version_info >= (3, 15): + detail: str + user_data: str if sys.version_info >= (3, 14): def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -312,21 +329,14 @@ class Variable: def trace_add(self, mode: Literal["array", "read", "write", "unset"], callback: Callable[[str, str, str], object]) -> str: ... def trace_remove(self, mode: Literal["array", "read", "write", "unset"], cbname: str) -> None: ... def trace_info(self) -> list[tuple[tuple[Literal["array", "read", "write", "unset"], ...], str]]: ... - if sys.version_info >= (3, 14): - @deprecated("Deprecated since Python 3.14. Use `trace_add()` instead.") - def trace(self, mode, callback) -> str: ... - @deprecated("Deprecated since Python 3.14. Use `trace_add()` instead.") - def trace_variable(self, mode, callback) -> str: ... - @deprecated("Deprecated since Python 3.14. Use `trace_remove()` instead.") - def trace_vdelete(self, mode, cbname) -> None: ... - @deprecated("Deprecated since Python 3.14. Use `trace_info()` instead.") - def trace_vinfo(self) -> list[Incomplete]: ... - else: - def trace(self, mode, callback) -> str: ... - def trace_variable(self, mode, callback) -> str: ... - def trace_vdelete(self, mode, cbname) -> None: ... - def trace_vinfo(self) -> list[Incomplete]: ... - + @deprecated("Deprecated since Python 3.14. Use `trace_add()` instead.") + def trace(self, mode, callback) -> str: ... + @deprecated("Deprecated since Python 3.14. Use `trace_add()` instead.") + def trace_variable(self, mode, callback) -> str: ... + @deprecated("Deprecated since Python 3.14. Use `trace_remove()` instead.") + def trace_vdelete(self, mode, cbname) -> None: ... + @deprecated("Deprecated since Python 3.14. Use `trace_info()` instead.") + def trace_vinfo(self) -> list[Incomplete]: ... def __eq__(self, other: object) -> bool: ... def __del__(self) -> None: ... __hash__: ClassVar[None] # type: ignore[assignment] @@ -363,6 +373,7 @@ getdouble = float def getboolean(s) -> bool: ... _Ts = TypeVarTuple("_Ts") +_P = ParamSpec("_P") @type_check_only class _GridIndexInfo(TypedDict, total=False): @@ -402,11 +413,19 @@ class Misc: def tk_focusFollowsMouse(self) -> None: ... def tk_focusNext(self) -> Misc | None: ... def tk_focusPrev(self) -> Misc | None: ... - # .after() can be called without the "func" argument, but it is basically never what you want. - # It behaves like time.sleep() and freezes the GUI app. - def after(self, ms: int | Literal["idle"], func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... - # after_idle is essentially partialmethod(after, "idle") - def after_idle(self, func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... + if sys.version_info >= (3, 14): + # .after() can be called without the "func" argument, but it is basically never what you want. + # It behaves like time.sleep() and freezes the GUI app. + def after(self, ms: int | Literal["idle"], func: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs) -> str: ... + # after_idle is essentially partialmethod(after, "idle") + def after_idle(self, func: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs) -> str: ... + else: + # .after() can be called without the "func" argument, but it is basically never what you want. + # It behaves like time.sleep() and freezes the GUI app. + def after(self, ms: int | Literal["idle"], func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... + # after_idle is essentially partialmethod(after, "idle") + def after_idle(self, func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) -> str: ... + def after_cancel(self, id: str) -> None: ... if sys.version_info >= (3, 13): def after_info(self, id: str | None = None) -> tuple[str, ...]: ... @@ -508,10 +527,12 @@ class Misc: def winfo_y(self) -> int: ... def update(self) -> None: ... def update_idletasks(self) -> None: ... + @overload def bindtags(self, tagList: None = None) -> tuple[str, ...]: ... @overload def bindtags(self, tagList: list[str] | tuple[str, ...]) -> None: ... + # bind with isinstance(func, str) doesn't return anything, but all other # binds do. The default value of func is not str. @overload @@ -525,6 +546,7 @@ class Misc: def bind(self, sequence: str | None, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... @overload def bind(self, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + # There's no way to know what type of widget bind_all and bind_class # callbacks will get, so those are Misc. @overload @@ -538,6 +560,7 @@ class Misc: def bind_all(self, sequence: str | None, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... @overload def bind_all(self, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + @overload def bind_class( self, @@ -550,6 +573,7 @@ class Misc: def bind_class(self, className: str, sequence: str | None, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... @overload def bind_class(self, className: str, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + def unbind(self, sequence: str, funcid: str | None = None) -> None: ... def unbind_all(self, sequence: str) -> None: ... def unbind_class(self, className: str, sequence: str) -> None: ... @@ -562,13 +586,16 @@ class Misc: self, func: Callable[..., object], subst: Callable[..., Sequence[Any]] | None = None, needcleanup: int = 1 ) -> str: ... def keys(self) -> list[str]: ... + @overload def pack_propagate(self, flag: bool) -> bool | None: ... @overload def pack_propagate(self) -> None: ... + propagate = pack_propagate def grid_anchor(self, anchor: Literal["nw", "n", "ne", "w", "center", "e", "sw", "s", "se"] | None = None) -> None: ... anchor = grid_anchor + @overload def grid_bbox( self, column: None = None, row: None = None, col2: None = None, row2: None = None @@ -577,6 +604,7 @@ class Misc: def grid_bbox(self, column: int, row: int, col2: None = None, row2: None = None) -> tuple[int, int, int, int] | None: ... @overload def grid_bbox(self, column: int, row: int, col2: int, row2: int) -> tuple[int, int, int, int] | None: ... + bbox = grid_bbox def grid_columnconfigure( self, @@ -601,10 +629,12 @@ class Misc: columnconfigure = grid_columnconfigure rowconfigure = grid_rowconfigure def grid_location(self, x: float | str, y: float | str) -> tuple[int, int]: ... + @overload def grid_propagate(self, flag: bool) -> None: ... @overload def grid_propagate(self) -> bool: ... + def grid_size(self) -> tuple[int, int]: ... size = grid_size # Widget because Toplevel or Tk is never a slave @@ -612,6 +642,12 @@ class Misc: def grid_slaves(self, row: int | None = None, column: int | None = None) -> list[Widget]: ... def place_slaves(self) -> list[Widget]: ... slaves = pack_slaves + if sys.version_info >= (3, 15): + def pack_content(self) -> list[Widget]: ... + def grid_content(self, row: int | None = None, column: int | None = None) -> list[Widget]: ... + def place_content(self) -> list[Widget]: ... + content = pack_content + def event_add(self, virtual: str, *sequences: str) -> None: ... def event_delete(self, virtual: str, *sequences: str) -> None: ... def event_generate( @@ -654,8 +690,7 @@ class Misc: def __getitem__(self, key: str) -> Any: ... def cget(self, key: str) -> Any: ... def configure(self, cnf: Any = None) -> Any: ... - # TODO: config is an alias of configure, but adding that here creates - # conflict with the type of config in the subclasses. See #13149 + config = configure class CallWrapper: func: Incomplete @@ -669,7 +704,9 @@ class XView: def xview(self) -> tuple[float, float]: ... @overload def xview(self, *args) -> None: ... + def xview_moveto(self, fraction: float) -> None: ... + @overload def xview_scroll(self, number: int, what: Literal["units", "pages"]) -> None: ... @overload @@ -680,7 +717,9 @@ class YView: def yview(self) -> tuple[float, float]: ... @overload def yview(self, *args) -> None: ... + def yview_moveto(self, fraction: float) -> None: ... + @overload def yview_scroll(self, number: int, what: Literal["units", "pages"]) -> None: ... @overload @@ -725,17 +764,20 @@ class Wm: def wm_aspect( self, minNumer: None = None, minDenom: None = None, maxNumer: None = None, maxDenom: None = None ) -> tuple[int, int, int, int] | None: ... + aspect = wm_aspect + + # wm_attributes: Get all attributes if sys.version_info >= (3, 13): @overload def wm_attributes(self, *, return_python_dict: Literal[False] = False) -> tuple[Any, ...]: ... @overload def wm_attributes(self, *, return_python_dict: Literal[True]) -> _WmAttributes: ... - else: @overload def wm_attributes(self) -> tuple[Any, ...]: ... + # wm_attributes: Get one attribute (old variant using string that starts with "-") @overload def wm_attributes(self, option: Literal["-alpha"], /) -> float: ... @overload @@ -767,6 +809,7 @@ class Wm: @overload def wm_attributes(self, option: Literal["-type"], /) -> str: ... if sys.version_info >= (3, 13): + # wm_attributes: Get one attribute (new variant without "-") @overload def wm_attributes(self, option: Literal["alpha"], /) -> float: ... @overload @@ -798,6 +841,7 @@ class Wm: @overload def wm_attributes(self, option: Literal["type"], /) -> str: ... + # wm_attributes: Set an attribute (old variant using string that starts with "-") @overload def wm_attributes(self, option: str, /): ... @overload @@ -829,8 +873,11 @@ class Wm: @overload def wm_attributes(self, option: Literal["-type"], value: str, /) -> Literal[""]: ... + # wm_attributes: Set multiple attributes (old variant using strings that start with "-") @overload def wm_attributes(self, option: str, value, /, *__other_option_value_pairs: Any) -> Literal[""]: ... + + # wm_attributes: Set an attribute (new variant with kwarg instead of string) if sys.version_info >= (3, 13): if sys.platform == "darwin": @overload @@ -867,12 +914,14 @@ class Wm: attributes = wm_attributes def wm_client(self, name: str | None = None) -> str: ... client = wm_client + @overload def wm_colormapwindows(self) -> list[Misc]: ... @overload def wm_colormapwindows(self, wlist: list[Misc] | tuple[Misc, ...], /) -> None: ... @overload def wm_colormapwindows(self, first_wlist_item: Misc, /, *other_wlist_items: Misc) -> None: ... + colormapwindows = wm_colormapwindows def wm_command(self, value: str | None = None) -> str: ... command = wm_command @@ -885,10 +934,12 @@ class Wm: forget = wm_forget def wm_frame(self) -> str: ... frame = wm_frame + @overload def wm_geometry(self, newGeometry: None = None) -> str: ... @overload def wm_geometry(self, newGeometry: str) -> None: ... + geometry = wm_geometry def wm_grid(self, baseWidth=None, baseHeight=None, widthInc=None, heightInc=None): ... grid = wm_grid @@ -910,51 +961,67 @@ class Wm: iconwindow = wm_iconwindow def wm_manage(self, widget) -> None: ... manage = wm_manage + @overload def wm_maxsize(self, width: None = None, height: None = None) -> tuple[int, int]: ... @overload def wm_maxsize(self, width: int, height: int) -> None: ... + maxsize = wm_maxsize + @overload def wm_minsize(self, width: None = None, height: None = None) -> tuple[int, int]: ... @overload def wm_minsize(self, width: int, height: int) -> None: ... + minsize = wm_minsize + @overload def wm_overrideredirect(self, boolean: None = None) -> bool | None: ... # returns True or None @overload def wm_overrideredirect(self, boolean: bool) -> None: ... + overrideredirect = wm_overrideredirect def wm_positionfrom(self, who: Literal["program", "user"] | None = None) -> Literal["", "program", "user"]: ... positionfrom = wm_positionfrom + @overload def wm_protocol(self, name: str, func: Callable[[], object] | str) -> None: ... @overload def wm_protocol(self, name: str, func: None = None) -> str: ... @overload def wm_protocol(self, name: None = None, func: None = None) -> tuple[str, ...]: ... + protocol = wm_protocol + @overload def wm_resizable(self, width: None = None, height: None = None) -> tuple[bool, bool]: ... @overload def wm_resizable(self, width: bool, height: bool) -> None: ... + resizable = wm_resizable def wm_sizefrom(self, who: Literal["program", "user"] | None = None) -> Literal["", "program", "user"]: ... sizefrom = wm_sizefrom + @overload def wm_state(self, newstate: None = None) -> str: ... @overload def wm_state(self, newstate: str) -> None: ... + state = wm_state + @overload def wm_title(self, string: None = None) -> str: ... @overload def wm_title(self, string: str) -> None: ... + title = wm_title + @overload def wm_transient(self, master: None = None) -> _tkinter.Tcl_Obj: ... @overload def wm_transient(self, master: Wm | _tkinter.Tcl_Obj) -> None: ... + transient = wm_transient def wm_withdraw(self) -> None: ... withdraw = wm_withdraw @@ -973,6 +1040,7 @@ class Tk(Misc, Wm): sync: bool = False, use: str | None = None, ) -> None: ... + # Keep this in sync with ttktheme.ThemedTk. See issue #13858 @overload def configure( @@ -998,6 +1066,7 @@ class Tk(Misc, Wm): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def destroy(self) -> None: ... def readprofile(self, baseName: str, className: str) -> None: ... @@ -1033,6 +1102,7 @@ class Tk(Misc, Wm): def splitlist(self, arg, /) -> tuple[Incomplete, ...]: ... def unsetvar(self, *args, **kwargs): ... + if sys.version_info >= (3, 14): @overload def wantobjects(self) -> Literal[0, 1]: ... @@ -1042,6 +1112,7 @@ class Tk(Misc, Wm): @overload def wantobjects(self, wantobjects: Literal[0, 1] | bool, /) -> None: ... + def willdispatch(self) -> None: ... def Tcl(screenName: str | None = None, baseName: str | None = None, className: str = "Tk", useTk: bool = False) -> Tk: ... @@ -1222,6 +1293,7 @@ class Toplevel(BaseWidget, Wm): visual: str | tuple[str, int] = "", width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -1246,6 +1318,7 @@ class Toplevel(BaseWidget, Wm): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Button(Widget): @@ -1297,6 +1370,7 @@ class Button(Widget): width: float | str = 0, wraplength: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -1341,6 +1415,7 @@ class Button(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def flash(self) -> None: ... def invoke(self) -> Any: ... @@ -1386,6 +1461,7 @@ class Canvas(Widget, XView, YView): yscrollcommand: str | Callable[[float, float], object] = "", yscrollincrement: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -1424,6 +1500,7 @@ class Canvas(Widget, XView, YView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def addtag(self, *args): ... # internal method def addtag_above(self, newtag: str, tagOrId: str | int) -> None: ... @@ -1447,6 +1524,7 @@ class Canvas(Widget, XView, YView): def find_withtag(self, tagOrId: str | int) -> tuple[int, ...]: ... # Incompatible with Misc.bbox(), tkinter violates LSP def bbox(self, *args: str | int) -> tuple[int, int, int, int]: ... # type: ignore[override] + @overload def tag_bind( self, @@ -1461,21 +1539,25 @@ class Canvas(Widget, XView, YView): ) -> None: ... @overload def tag_bind(self, tagOrId: str | int, *, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + def tag_unbind(self, tagOrId: str | int, sequence: str, funcid: str | None = None) -> None: ... - def canvasx(self, screenx, gridspacing=None): ... - def canvasy(self, screeny, gridspacing=None): ... + def canvasx(self, screenx: float | str, gridspacing: float | str | None = None) -> float: ... + def canvasy(self, screeny: float | str, gridspacing: float | str | None = None) -> float: ... + @overload def coords(self, tagOrId: str | int, /) -> list[float]: ... @overload def coords(self, tagOrId: str | int, args: list[int] | list[float] | tuple[float, ...], /) -> None: ... @overload def coords(self, tagOrId: str | int, x1: float, y1: float, /, *args: float) -> None: ... + # create_foo() methods accept coords as a list or tuple, or as separate arguments. # Lists and tuples can be flat as in [1, 2, 3, 4], or nested as in [(1, 2), (3, 4)]. # Keyword arguments should be the same in all overloads of each method. def create_arc(self, *args, **kw) -> int: ... def create_bitmap(self, *args, **kw) -> int: ... def create_image(self, *args, **kw) -> int: ... + @overload def create_line( self, @@ -1574,6 +1656,7 @@ class Canvas(Widget, XView, YView): tags: str | list[str] | tuple[str, ...] = ..., width: float | str = ..., ) -> int: ... + @overload def create_oval( self, @@ -1675,6 +1758,7 @@ class Canvas(Widget, XView, YView): tags: str | list[str] | tuple[str, ...] = ..., width: float | str = ..., ) -> int: ... + @overload def create_polygon( self, @@ -1785,6 +1869,7 @@ class Canvas(Widget, XView, YView): tags: str | list[str] | tuple[str, ...] = ..., width: float | str = ..., ) -> int: ... + @overload def create_rectangle( self, @@ -1886,6 +1971,7 @@ class Canvas(Widget, XView, YView): tags: str | list[str] | tuple[str, ...] = ..., width: float | str = ..., ) -> int: ... + @overload def create_text( self, @@ -1931,6 +2017,7 @@ class Canvas(Widget, XView, YView): text: float | str = ..., width: float | str = ..., ) -> int: ... + @overload def create_window( self, @@ -1958,12 +2045,15 @@ class Canvas(Widget, XView, YView): width: float | str = ..., window: Widget = ..., ) -> int: ... + def dchars(self, *args) -> None: ... def delete(self, *tagsOrCanvasIds: str | int) -> None: ... + @overload def dtag(self, tag: str, tag_to_delete: str | None = ..., /) -> None: ... @overload def dtag(self, id: int, tag_to_delete: str, /) -> None: ... + def focus(self, *args): ... def gettags(self, tagOrId: str | int, /) -> tuple[str, ...]: ... def icursor(self, *args) -> None: ... @@ -2058,6 +2148,7 @@ class Checkbutton(Widget): width: float | str = 0, wraplength: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2108,6 +2199,7 @@ class Checkbutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def deselect(self) -> None: ... def flash(self) -> None: ... @@ -2160,6 +2252,7 @@ class Entry(Widget, XView): width: int = 20, xscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload def configure( self, @@ -2205,6 +2298,7 @@ class Entry(Widget, XView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def delete(self, first: str | int, last: str | int | None = None) -> None: ... def get(self) -> str: ... @@ -2253,6 +2347,7 @@ class Frame(Widget): visual: str | tuple[str, int] = "", # can't be changed with configure() width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2276,6 +2371,7 @@ class Frame(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Label(Widget): @@ -2317,6 +2413,7 @@ class Label(Widget): width: float | str = 0, wraplength: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2356,6 +2453,7 @@ class Label(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Listbox(Widget, XView, YView): @@ -2410,6 +2508,7 @@ class Listbox(Widget, XView, YView): xscrollcommand: str | Callable[[float, float], object] = "", yscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload def configure( self, @@ -2447,6 +2546,7 @@ class Listbox(Widget, XView, YView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def activate(self, index: str | int) -> None: ... def bbox(self, index: str | int) -> tuple[int, int, int, int] | None: ... # type: ignore[override] @@ -2504,6 +2604,7 @@ class Menu(Widget): title: str = "", type: Literal["menubar", "tearoff", "normal"] = "normal", ) -> None: ... + @overload def configure( self, @@ -2533,6 +2634,7 @@ class Menu(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def tk_popup(self, x: int, y: int, entry: str | int = "") -> None: ... def activate(self, index: str | int) -> None: ... @@ -2784,6 +2886,7 @@ class Menubutton(Widget): width: float | str = 0, wraplength: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2826,6 +2929,7 @@ class Menubutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Message(Widget): @@ -2859,6 +2963,7 @@ class Message(Widget): # there's width but no height width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2889,6 +2994,7 @@ class Message(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Radiobutton(Widget): @@ -2940,6 +3046,7 @@ class Radiobutton(Widget): width: float | str = 0, wraplength: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -2989,6 +3096,7 @@ class Radiobutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def deselect(self) -> None: ... def flash(self) -> None: ... @@ -3038,6 +3146,7 @@ class Scale(Widget): variable: IntVar | DoubleVar = ..., width: float | str = 15, ) -> None: ... + @overload def configure( self, @@ -3080,6 +3189,7 @@ class Scale(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def get(self) -> float: ... def set(self, value) -> None: ... @@ -3119,6 +3229,7 @@ class Scrollbar(Widget): troughcolor: str = ..., width: float | str = ..., ) -> None: ... + @overload def configure( self, @@ -3148,6 +3259,7 @@ class Scrollbar(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def activate(self, index=None): ... def delta(self, deltax: int, deltay: int) -> float: ... @@ -3217,6 +3329,7 @@ class Text(Widget, XView, YView): xscrollcommand: str | Callable[[float, float], object] = "", yscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload def configure( self, @@ -3270,6 +3383,7 @@ class Text(Widget, XView, YView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def bbox(self, index: str | float | _tkinter.Tcl_Obj | Widget) -> tuple[int, int, int, int] | None: ... # type: ignore[override] def compare( @@ -3278,6 +3392,7 @@ class Text(Widget, XView, YView): op: Literal["<", "<=", "==", ">=", ">", "!="], index2: str | float | _tkinter.Tcl_Obj | Widget, ) -> bool: ... + if sys.version_info >= (3, 13): @overload def count( @@ -3461,10 +3576,12 @@ class Text(Widget, XView, YView): def debug(self, boolean: None = None) -> bool: ... @overload def debug(self, boolean: bool) -> None: ... + def delete( self, index1: str | float | _tkinter.Tcl_Obj | Widget, index2: str | float | _tkinter.Tcl_Obj | Widget | None = None ) -> None: ... def dlineinfo(self, index: str | float | _tkinter.Tcl_Obj | Widget) -> tuple[int, int, int, int, int] | None: ... + @overload def dump( self, @@ -3507,11 +3624,14 @@ class Text(Widget, XView, YView): text: bool = ..., window: bool = ..., ) -> None: ... + def edit(self, *args): ... # docstring says "Internal method" + @overload def edit_modified(self, arg: None = None) -> bool: ... # actually returns Literal[0, 1] @overload def edit_modified(self, arg: bool) -> None: ... # actually returns empty string + def edit_redo(self) -> None: ... # actually returns empty string def edit_reset(self) -> None: ... # actually returns empty string def edit_separator(self) -> None: ... # actually returns empty string @@ -3519,6 +3639,7 @@ class Text(Widget, XView, YView): def get( self, index1: str | float | _tkinter.Tcl_Obj | Widget, index2: str | float | _tkinter.Tcl_Obj | Widget | None = None ) -> str: ... + @overload def image_cget(self, index: str | float | _tkinter.Tcl_Obj | Widget, option: Literal["image", "name"]) -> str: ... @overload @@ -3529,6 +3650,7 @@ class Text(Widget, XView, YView): ) -> Literal["baseline", "bottom", "center", "top"]: ... @overload def image_cget(self, index: str | float | _tkinter.Tcl_Obj | Widget, option: str) -> Any: ... + @overload def image_configure( self, index: str | float | _tkinter.Tcl_Obj | Widget, cnf: str @@ -3545,6 +3667,7 @@ class Text(Widget, XView, YView): padx: float | str = ..., pady: float | str = ..., ) -> dict[str, tuple[str, str, str, str, str | int]] | None: ... + def image_create( self, index: str | float | _tkinter.Tcl_Obj | Widget, @@ -3561,10 +3684,12 @@ class Text(Widget, XView, YView): def insert( self, index: str | float | _tkinter.Tcl_Obj | Widget, chars: str, *args: str | list[str] | tuple[str, ...] ) -> None: ... + @overload def mark_gravity(self, markName: str, direction: None = None) -> Literal["left", "right"]: ... @overload def mark_gravity(self, markName: str, direction: Literal["left", "right"]) -> None: ... # actually returns empty string + def mark_names(self) -> tuple[str, ...]: ... def mark_set(self, markName: str, index: str | float | _tkinter.Tcl_Obj | Widget) -> None: ... def mark_unset(self, *markNames: str) -> None: ... @@ -3582,23 +3707,60 @@ class Text(Widget, XView, YView): ) -> None: ... def scan_mark(self, x: int, y: int) -> None: ... def scan_dragto(self, x: int, y: int) -> None: ... - def search( - self, - pattern: str, - index: str | float | _tkinter.Tcl_Obj | Widget, - stopindex: str | float | _tkinter.Tcl_Obj | Widget | None = None, - forwards: bool | None = None, - backwards: bool | None = None, - exact: bool | None = None, - regexp: bool | None = None, - nocase: bool | None = None, - count: Variable | None = None, - elide: bool | None = None, - ) -> str: ... # returns empty string for not found + if sys.version_info >= (3, 15): + def search( + self, + pattern: str, + index: str | float | _tkinter.Tcl_Obj | Widget, + stopindex: str | float | _tkinter.Tcl_Obj | Widget | None = None, + forwards: bool | None = None, + backwards: bool | None = None, + exact: bool | None = None, + regexp: bool | None = None, + nocase: bool | None = None, + count: Variable | None = None, + elide: bool | None = None, + *, + nolinestop: bool | None = None, + strictlimits: bool | None = None, + ) -> str: ... # returns empty string for not found + def search_all( + self, + pattern: str, + index: str | float | _tkinter.Tcl_Obj | Widget, + stopindex: str | float | _tkinter.Tcl_Obj | Widget | None = None, + *, + forwards: bool | None = None, + backwards: bool | None = None, + exact: bool | None = None, + regexp: bool | None = None, + nocase: bool | None = None, + count: Variable | None = None, + elide: bool | None = None, + nolinestop: bool | None = None, + overlap: bool | None = None, + strictlimits: bool | None = None, + ) -> tuple[_tkinter.Tcl_Obj, ...]: ... + else: + def search( + self, + pattern: str, + index: str | float | _tkinter.Tcl_Obj | Widget, + stopindex: str | float | _tkinter.Tcl_Obj | Widget | None = None, + forwards: bool | None = None, + backwards: bool | None = None, + exact: bool | None = None, + regexp: bool | None = None, + nocase: bool | None = None, + count: Variable | None = None, + elide: bool | None = None, + ) -> str: ... # returns empty string for not found + def see(self, index: str | float | _tkinter.Tcl_Obj | Widget) -> None: ... def tag_add( self, tagName: str, index1: str | float | _tkinter.Tcl_Obj | Widget, *args: str | float | _tkinter.Tcl_Obj | Widget ) -> None: ... + # tag_bind stuff is very similar to Canvas @overload def tag_bind( @@ -3610,9 +3772,11 @@ class Text(Widget, XView, YView): ) -> str: ... @overload def tag_bind(self, tagName: str, sequence: str | None, func: str, add: Literal["", "+"] | bool | None = None) -> None: ... + def tag_unbind(self, tagName: str, sequence: str, funcid: str | None = None) -> None: ... # allowing any string for cget instead of just Literals because there's no other way to look up tag options def tag_cget(self, tagName: str, option: str): ... + @overload def tag_configure( self, @@ -3650,6 +3814,7 @@ class Text(Widget, XView, YView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def tag_configure(self, tagName: str, cnf: str) -> tuple[str, str, str, Any, Any]: ... + tag_config = tag_configure def tag_delete(self, first_tag_name: str, /, *tagNames: str) -> None: ... # error if no tag names given def tag_lower(self, tagName: str, belowThis: str | None = None) -> None: ... @@ -3675,6 +3840,7 @@ class Text(Widget, XView, YView): index1: str | float | _tkinter.Tcl_Obj | Widget, index2: str | float | _tkinter.Tcl_Obj | Widget | None = None, ) -> None: ... + @overload def window_cget(self, index: str | float | _tkinter.Tcl_Obj | Widget, option: Literal["padx", "pady"]) -> int: ... @overload @@ -3689,6 +3855,7 @@ class Text(Widget, XView, YView): def window_cget(self, index: str | float | _tkinter.Tcl_Obj | Widget, option: Literal["create", "window"]) -> str: ... @overload def window_cget(self, index: str | float | _tkinter.Tcl_Obj | Widget, option: str) -> Any: ... + @overload def window_configure( self, index: str | float | _tkinter.Tcl_Obj | Widget, cnf: str @@ -3706,6 +3873,7 @@ class Text(Widget, XView, YView): stretch: bool | Literal[0, 1] = ..., window: Misc | str = ..., ) -> dict[str, tuple[str, str, str, str, str | int]] | None: ... + window_config = window_configure def window_create( self, @@ -3729,16 +3897,27 @@ class _setit: # manual page: tk_optionMenu class OptionMenu(Menubutton): menuname: Incomplete - def __init__( - # differs from other widgets - self, - master: Misc | None, - variable: StringVar, - value: str, - *values: str, - # kwarg only from now on - command: Callable[[StringVar], object] | None = ..., - ) -> None: ... + if sys.version_info >= (3, 14): + def __init__( + # differs from other widgets + self, + master: Misc | None, + variable: StringVar, + value: str, + *values: str, + command: Callable[[StringVar], object] | None = ..., + name: str | None = None, + ) -> None: ... + else: + def __init__( + # differs from other widgets + self, + master: Misc | None, + variable: StringVar, + value: str, + *values: str, + command: Callable[[StringVar], object] | None = ..., + ) -> None: ... # configure, config, cget are inherited from Menubutton # destroy and __getitem__ are overridden, signature does not change @@ -3807,8 +3986,8 @@ class PhotoImage(Image, _PhotoImageLike): zoom: int | tuple[int, int] | list[int] | None = None, subsample: int | tuple[int, int] | list[int] | None = None, ) -> PhotoImage: ... - def subsample(self, x: int, y: Literal[""] = "", *, from_coords: Iterable[int] | None = None) -> PhotoImage: ... - def zoom(self, x: int, y: Literal[""] = "", *, from_coords: Iterable[int] | None = None) -> PhotoImage: ... + def subsample(self, x: int, y: int | Literal[""] = "", *, from_coords: Iterable[int] | None = None) -> PhotoImage: ... + def zoom(self, x: int, y: int | Literal[""] = "", *, from_coords: Iterable[int] | None = None) -> PhotoImage: ... def copy_replace( self, sourceImage: PhotoImage | str, @@ -3860,6 +4039,7 @@ class PhotoImage(Image, _PhotoImageLike): background: str | None = None, grayscale: bool = False, ) -> None: ... + @overload def data( self, format: str, *, from_coords: Iterable[int] | None = None, background: str | None = None, grayscale: bool = False @@ -3960,6 +4140,7 @@ class Spinbox(Widget, XView): wrap: bool = False, xscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload def configure( self, @@ -4018,6 +4199,7 @@ class Spinbox(Widget, XView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def bbox(self, index) -> tuple[int, int, int, int] | None: ... # type: ignore[override] def delete(self, first, last=None) -> Literal[""]: ... @@ -4074,6 +4256,7 @@ class LabelFrame(Widget): visual: str | tuple[str, int] = "", # can't be changed with configure() width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -4103,6 +4286,7 @@ class LabelFrame(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class PanedWindow(Widget): @@ -4134,6 +4318,7 @@ class PanedWindow(Widget): showhandle: bool = False, width: float | str = "", ) -> None: ... + @overload def configure( self, @@ -4163,6 +4348,7 @@ class PanedWindow(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def add(self, child: Widget, **kw) -> None: ... def remove(self, child) -> None: ... diff --git a/mypy/typeshed/stdlib/tkinter/font.pyi b/mypy/typeshed/stdlib/tkinter/font.pyi index 327ba7a2432e0..d9e0b13155909 100644 --- a/mypy/typeshed/stdlib/tkinter/font.pyi +++ b/mypy/typeshed/stdlib/tkinter/font.pyi @@ -1,9 +1,8 @@ import _tkinter import itertools -import sys import tkinter -from typing import Any, ClassVar, Final, Literal, TypedDict, overload, type_check_only -from typing_extensions import TypeAlias, Unpack +from typing import Any, ClassVar, Final, Literal, TypeAlias, TypedDict, overload, type_check_only +from typing_extensions import Unpack __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC", "nametofont", "Font", "families", "names"] @@ -61,6 +60,7 @@ class Font: ) -> None: ... __hash__: ClassVar[None] # type: ignore[assignment] def __setitem__(self, key: str, value: Any) -> None: ... + @overload def cget(self, option: Literal["family"]) -> str: ... @overload @@ -73,7 +73,9 @@ class Font: def cget(self, option: Literal["underline", "overstrike"]) -> bool: ... @overload def cget(self, option: str) -> Any: ... + __getitem__ = cget + @overload def actual(self, option: Literal["family"], displayof: tkinter.Misc | None = None) -> str: ... @overload @@ -88,6 +90,7 @@ class Font: def actual(self, option: None, displayof: tkinter.Misc | None = None) -> _FontDict: ... @overload def actual(self, *, displayof: tkinter.Misc | None = None) -> _FontDict: ... + def config( self, *, @@ -100,21 +103,18 @@ class Font: ) -> _FontDict | None: ... configure = config def copy(self) -> Font: ... + @overload def metrics(self, option: Literal["ascent", "descent", "linespace"], /, *, displayof: tkinter.Misc | None = ...) -> int: ... @overload def metrics(self, option: Literal["fixed"], /, *, displayof: tkinter.Misc | None = ...) -> bool: ... @overload def metrics(self, *, displayof: tkinter.Misc | None = ...) -> _MetricsDict: ... + def measure(self, text: str, displayof: tkinter.Misc | None = None) -> int: ... def __eq__(self, other: object) -> bool: ... def __del__(self) -> None: ... def families(root: tkinter.Misc | None = None, displayof: tkinter.Misc | None = None) -> tuple[str, ...]: ... def names(root: tkinter.Misc | None = None) -> tuple[str, ...]: ... - -if sys.version_info >= (3, 10): - def nametofont(name: str, root: tkinter.Misc | None = None) -> Font: ... - -else: - def nametofont(name: str) -> Font: ... +def nametofont(name: str, root: tkinter.Misc | None = None) -> Font: ... diff --git a/mypy/typeshed/stdlib/tkinter/simpledialog.pyi b/mypy/typeshed/stdlib/tkinter/simpledialog.pyi index 45dce21a6b1c3..6f66f0237b458 100644 --- a/mypy/typeshed/stdlib/tkinter/simpledialog.pyi +++ b/mypy/typeshed/stdlib/tkinter/simpledialog.pyi @@ -1,5 +1,9 @@ +import sys from tkinter import Event, Frame, Misc, Toplevel +if sys.version_info >= (3, 15): + __all__ = ["SimpleDialog", "Dialog", "askinteger", "askfloat", "askstring"] + class Dialog(Toplevel): def __init__(self, parent: Misc | None, title: str | None = None) -> None: ... def body(self, master: Frame) -> Misc | None: ... diff --git a/mypy/typeshed/stdlib/tkinter/ttk.pyi b/mypy/typeshed/stdlib/tkinter/ttk.pyi index 0a6837201035c..06d2be13dc283 100644 --- a/mypy/typeshed/stdlib/tkinter/ttk.pyi +++ b/mypy/typeshed/stdlib/tkinter/ttk.pyi @@ -4,8 +4,8 @@ import tkinter from _typeshed import MaybeNone from collections.abc import Callable, Iterable, Sequence from tkinter.font import _FontDescription -from typing import Any, Literal, TypedDict, TypeVar, overload, type_check_only -from typing_extensions import Never, ParamSpec, TypeAlias, Unpack +from typing import Any, Literal, ParamSpec, TypeAlias, TypedDict, TypeVar, overload, type_check_only +from typing_extensions import Never, Unpack __all__ = [ "Button", @@ -123,6 +123,7 @@ class Style: master: tkinter.Misc tk: _tkinter.TkappType def __init__(self, master: tkinter.Misc | None = None) -> None: ... + # For these methods, values given vary between options. Returned values # seem to be str, but this might not always be the case. @overload @@ -131,15 +132,19 @@ class Style: def configure(self, style: str, query_opt: str, **kw: Any) -> Any: ... @overload def configure(self, style: str, query_opt: None = None, **kw: Any) -> None: ... + @overload def map(self, style: str, query_opt: str) -> _Statespec: ... @overload def map(self, style: str, query_opt: None = None, **kw: Iterable[_Statespec]) -> dict[str, _Statespec]: ... + def lookup(self, style: str, option: str, state: Iterable[str] | None = None, default: Any | None = None) -> Any: ... + @overload def layout(self, style: str, layoutspec: _LayoutSpec) -> list[Never]: ... # Always seems to return an empty list @overload def layout(self, style: str, layoutspec: None = None) -> _LayoutSpec: ... + @overload def element_create( self, @@ -203,6 +208,7 @@ class Style: def theme_create(self, themename: str, parent: str | None = None, settings: _ThemeSettings | None = None) -> None: ... def theme_settings(self, themename: str, settings: _ThemeSettings) -> None: ... def theme_names(self) -> tuple[str, ...]: ... + @overload def theme_use(self, themename: str) -> None: ... @overload @@ -211,12 +217,14 @@ class Style: class Widget(tkinter.Widget): def __init__(self, master: tkinter.Misc | None, widgetname: str | None, kw: dict[str, Any] | None = None) -> None: ... def identify(self, x: int, y: int) -> str: ... + @overload def instate(self, statespec: Sequence[str], callback: None = None) -> bool: ... @overload def instate( self, statespec: Sequence[str], callback: Callable[_P, _T], *args: _P.args, **kw: _P.kwargs ) -> Literal[False] | _T: ... + def state(self, statespec: Sequence[str] | None = None) -> tuple[str, ...]: ... class Button(Widget): @@ -240,6 +248,7 @@ class Button(Widget): underline: int = -1, width: int | Literal[""] = "", ) -> None: ... + @overload def configure( self, @@ -261,6 +270,7 @@ class Button(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def invoke(self) -> Any: ... @@ -290,6 +300,7 @@ class Checkbutton(Widget): variable: tkinter.Variable = ..., width: int | Literal[""] = "", ) -> None: ... + @overload def configure( self, @@ -313,6 +324,7 @@ class Checkbutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def invoke(self) -> Any: ... @@ -341,6 +353,7 @@ class Entry(Widget, tkinter.Entry): width: int = 20, xscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -365,6 +378,7 @@ class Entry(Widget, tkinter.Entry): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + # config must be copy/pasted, otherwise ttk.Entry().config is mypy error (don't know why) @overload # type: ignore[override] def config( @@ -390,6 +404,7 @@ class Entry(Widget, tkinter.Entry): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + def bbox(self, index) -> tuple[int, int, int, int]: ... # type: ignore[override] def identify(self, x: int, y: int) -> str: ... def validate(self): ... @@ -421,6 +436,7 @@ class Combobox(Entry): width: int = 20, xscrollcommand: str | Callable[[float, float], object] = ..., # undocumented ) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -448,6 +464,7 @@ class Combobox(Entry): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + # config must be copy/pasted, otherwise ttk.Combobox().config is mypy error (don't know why) @overload # type: ignore[override] def config( @@ -476,6 +493,7 @@ class Combobox(Entry): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + def current(self, newindex: int | None = None) -> int: ... def set(self, value: Any) -> None: ... @@ -498,6 +516,7 @@ class Frame(Widget): takefocus: bool | Literal[0, 1, ""] | Callable[[str], bool | None] = "", width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -515,6 +534,7 @@ class Frame(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Label(Widget): @@ -545,6 +565,7 @@ class Label(Widget): width: int | Literal[""] = "", wraplength: float | str = ..., ) -> None: ... + @overload def configure( self, @@ -573,6 +594,7 @@ class Label(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Labelframe(Widget): @@ -596,6 +618,7 @@ class Labelframe(Widget): underline: int = -1, width: float | str = 0, ) -> None: ... + @overload def configure( self, @@ -617,6 +640,7 @@ class Labelframe(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure LabelFrame = Labelframe @@ -642,6 +666,7 @@ class Menubutton(Widget): underline: int = -1, width: int | Literal[""] = "", ) -> None: ... + @overload def configure( self, @@ -663,6 +688,7 @@ class Menubutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Notebook(Widget): @@ -679,6 +705,7 @@ class Notebook(Widget): takefocus: bool | Literal[0, 1, ""] | Callable[[str], bool | None] = ..., width: int = 0, ) -> None: ... + @overload def configure( self, @@ -693,6 +720,7 @@ class Notebook(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def add( self, @@ -734,6 +762,7 @@ class Panedwindow(Widget, tkinter.PanedWindow): width: int = 0, ) -> None: ... def add(self, child: tkinter.Widget, *, weight: int = ..., **kw) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -747,6 +776,7 @@ class Panedwindow(Widget, tkinter.PanedWindow): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + # config must be copy/pasted, otherwise ttk.Panedwindow().config is mypy error (don't know why) @overload # type: ignore[override] def config( @@ -761,6 +791,7 @@ class Panedwindow(Widget, tkinter.PanedWindow): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + forget = tkinter.PanedWindow.forget def insert(self, pos, child, **kw) -> None: ... def pane(self, pane, option=None, **kw): ... @@ -786,6 +817,7 @@ class Progressbar(Widget): value: float = 0.0, variable: tkinter.IntVar | tkinter.DoubleVar = ..., ) -> None: ... + @overload def configure( self, @@ -804,6 +836,7 @@ class Progressbar(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def start(self, interval: Literal["idle"] | int | None = None) -> None: ... def step(self, amount: float | None = None) -> None: ... @@ -831,6 +864,7 @@ class Radiobutton(Widget): variable: tkinter.Variable | Literal[""] = ..., width: int | Literal[""] = "", ) -> None: ... + @overload def configure( self, @@ -853,6 +887,7 @@ class Radiobutton(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def invoke(self) -> Any: ... @@ -876,6 +911,7 @@ class Scale(Widget, tkinter.Scale): # type: ignore[misc] value: float = 0, variable: tkinter.IntVar | tkinter.DoubleVar = ..., ) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -895,6 +931,7 @@ class Scale(Widget, tkinter.Scale): # type: ignore[misc] ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + # config must be copy/pasted, otherwise ttk.Scale().config is mypy error (don't know why) @overload # type: ignore[override] def config( @@ -915,6 +952,7 @@ class Scale(Widget, tkinter.Scale): # type: ignore[misc] ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + def get(self, x: int | None = None, y: int | None = None) -> float: ... # type ignore, because identify() methods of Widget and tkinter.Scale are incompatible @@ -931,6 +969,7 @@ class Scrollbar(Widget, tkinter.Scrollbar): # type: ignore[misc] style: str = "", takefocus: bool | Literal[0, 1, ""] | Callable[[str], bool | None] = "", ) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -944,6 +983,7 @@ class Scrollbar(Widget, tkinter.Scrollbar): # type: ignore[misc] ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + # config must be copy/pasted, otherwise ttk.Scrollbar().config is mypy error (don't know why) @overload # type: ignore[override] def config( @@ -971,6 +1011,7 @@ class Separator(Widget): style: str = "", takefocus: bool | Literal[0, 1, ""] | Callable[[str], bool | None] = "", ) -> None: ... + @overload def configure( self, @@ -983,6 +1024,7 @@ class Separator(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Sizegrip(Widget): @@ -996,6 +1038,7 @@ class Sizegrip(Widget): style: str = "", takefocus: bool | Literal[0, 1, ""] | Callable[[str], bool | None] = "", ) -> None: ... + @overload def configure( self, @@ -1007,6 +1050,7 @@ class Sizegrip(Widget): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure class Spinbox(Entry): @@ -1040,6 +1084,7 @@ class Spinbox(Entry): wrap: bool = False, xscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload # type: ignore[override] def configure( self, @@ -1071,6 +1116,7 @@ class Spinbox(Entry): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure # type: ignore[assignment] def set(self, value: Any) -> None: ... @@ -1129,6 +1175,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): xscrollcommand: str | Callable[[float, float], object] = "", yscrollcommand: str | Callable[[float, float], object] = "", ) -> None: ... + @overload def configure( self, @@ -1148,10 +1195,12 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def configure(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... + config = configure def bbox(self, item: str | int, column: str | int | None = None) -> tuple[int, int, int, int] | Literal[""]: ... # type: ignore[override] def get_children(self, item: str | int | None = None) -> tuple[str, ...]: ... def set_children(self, item: str | int, *newchildren: str | int) -> None: ... + @overload def column(self, column: str | int, option: Literal["width", "minwidth"]) -> int: ... @overload @@ -1174,13 +1223,16 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): anchor: Literal["nw", "n", "ne", "w", "center", "e", "sw", "s", "se"] = ..., # id is read-only ) -> _TreeviewColumnDict | None: ... + def delete(self, *items: str | int) -> None: ... def detach(self, *items: str | int) -> None: ... def exists(self, item: str | int) -> bool: ... + @overload # type: ignore[override] def focus(self, item: None = None) -> str: ... # can return empty string @overload def focus(self, item: str | int) -> Literal[""]: ... + @overload def heading(self, column: str | int, option: Literal["text"]) -> str: ... @overload @@ -1204,6 +1256,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): anchor: Literal["nw", "n", "ne", "w", "center", "e", "sw", "s", "se"] = ..., command: str | Callable[[], object] = ..., ) -> None: ... + # Internal Method. Leave untyped: def identify(self, component, x, y): ... # type: ignore[override] def identify_row(self, y: int) -> str: ... @@ -1224,6 +1277,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): open: bool = ..., tags: str | list[str] | tuple[str, ...] = ..., ) -> str: ... + @overload def item(self, item: str | int, option: Literal["text"]) -> str: ... @overload @@ -1250,6 +1304,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): open: bool = ..., tags: str | list[str] | tuple[str, ...] = ..., ) -> None: ... + def move(self, item: str | int, parent: str, index: int | Literal["end"]) -> None: ... reattach = move def next(self, item: str | int) -> str: ... # returning empty string means last item @@ -1257,28 +1312,34 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): def prev(self, item: str | int) -> str: ... # returning empty string means first item def see(self, item: str | int) -> None: ... def selection(self) -> tuple[str, ...]: ... + @overload def selection_set(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... @overload def selection_set(self, *items: str | int) -> None: ... + @overload def selection_add(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... @overload def selection_add(self, *items: str | int) -> None: ... + @overload def selection_remove(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... @overload def selection_remove(self, *items: str | int) -> None: ... + @overload def selection_toggle(self, items: list[str] | tuple[str, ...] | list[int] | tuple[int, ...], /) -> None: ... @overload def selection_toggle(self, *items: str | int) -> None: ... + @overload def set(self, item: str | int, column: None = None, value: None = None) -> dict[str, Any]: ... @overload def set(self, item: str | int, column: str | int, value: None = None) -> Any: ... @overload def set(self, item: str | int, column: str | int, value: Any) -> Literal[""]: ... + # There's no tag_unbind() or 'add' argument for whatever reason. # Also, it's 'callback' instead of 'func' here. @overload @@ -1289,6 +1350,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): def tag_bind(self, tagname: str, sequence: str | None, callback: str) -> None: ... @overload def tag_bind(self, tagname: str, *, callback: str) -> None: ... + @overload def tag_configure(self, tagname: str, option: Literal["foreground", "background"]) -> str: ... @overload @@ -1307,6 +1369,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView): font: _FontDescription = ..., image: tkinter._Image | str = ..., ) -> _TreeviewTagDict | MaybeNone: ... # can be None but annoying to check + @overload def tag_has(self, tagname: str, item: None = None) -> tuple[str, ...]: ... @overload @@ -1341,17 +1404,31 @@ class LabeledScale(Frame): value: Any class OptionMenu(Menubutton): - def __init__( - self, - master: tkinter.Misc | None, - variable: tkinter.StringVar, - default: str | None = None, - *values: str, - # rest of these are keyword-only because *args syntax used above - style: str = "", - direction: Literal["above", "below", "left", "right", "flush"] = "below", - command: Callable[[tkinter.StringVar], object] | None = None, - ) -> None: ... + if sys.version_info >= (3, 14): + def __init__( + self, + master: tkinter.Misc | None, + variable: tkinter.StringVar, + default: str | None = None, + *values: str, + # rest of these are keyword-only because *args syntax used above + style: str = "", + direction: Literal["above", "below", "left", "right", "flush"] = "below", + command: Callable[[tkinter.StringVar], object] | None = None, + name: str | None = None, + ) -> None: ... + else: + def __init__( + self, + master: tkinter.Misc | None, + variable: tkinter.StringVar, + default: str | None = None, + *values: str, + # rest of these are keyword-only because *args syntax used above + style: str = "", + direction: Literal["above", "below", "left", "right", "flush"] = "below", + command: Callable[[tkinter.StringVar], object] | None = None, + ) -> None: ... # configure, config, cget, destroy are inherited from Menubutton # destroy and __setitem__ are overridden, signature does not change def set_menu(self, default: str | None = None, *values: str) -> None: ... diff --git a/mypy/typeshed/stdlib/token.pyi b/mypy/typeshed/stdlib/token.pyi index fd1b10da1d12e..23b43250d60fe 100644 --- a/mypy/typeshed/stdlib/token.pyi +++ b/mypy/typeshed/stdlib/token.pyi @@ -57,6 +57,7 @@ __all__ = [ "SEMI", "SLASH", "SLASHEQUAL", + "SOFT_KEYWORD", "STAR", "STAREQUAL", "STRING", @@ -73,9 +74,6 @@ __all__ = [ if sys.version_info < (3, 13): __all__ += ["ASYNC", "AWAIT"] -if sys.version_info >= (3, 10): - __all__ += ["SOFT_KEYWORD"] - if sys.version_info >= (3, 12): __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START", "EXACT_TOKEN_TYPES"] @@ -150,8 +148,7 @@ TYPE_COMMENT: Final[int] TYPE_IGNORE: Final[int] COLONEQUAL: Final[int] EXACT_TOKEN_TYPES: Final[dict[str, int]] -if sys.version_info >= (3, 10): - SOFT_KEYWORD: Final[int] +SOFT_KEYWORD: Final[int] if sys.version_info >= (3, 12): EXCLAMATION: Final[int] diff --git a/mypy/typeshed/stdlib/tokenize.pyi b/mypy/typeshed/stdlib/tokenize.pyi index 0df8062d56891..0aa3947a178d5 100644 --- a/mypy/typeshed/stdlib/tokenize.pyi +++ b/mypy/typeshed/stdlib/tokenize.pyi @@ -3,8 +3,8 @@ from _typeshed import FileDescriptorOrPath from collections.abc import Callable, Generator, Iterable, Sequence from re import Pattern from token import * -from typing import Any, Final, NamedTuple, TextIO, type_check_only -from typing_extensions import TypeAlias, disjoint_base +from typing import Any, Final, NamedTuple, TextIO, TypeAlias, type_check_only +from typing_extensions import disjoint_base if sys.version_info < (3, 12): # Avoid double assignment to Final name by imports, which pyright objects to. @@ -71,6 +71,7 @@ __all__ = [ "SEMI", "SLASH", "SLASHEQUAL", + "SOFT_KEYWORD", "STAR", "STAREQUAL", "STRING", @@ -89,9 +90,6 @@ __all__ = [ if sys.version_info < (3, 13): __all__ += ["ASYNC", "AWAIT"] -if sys.version_info >= (3, 10): - __all__ += ["SOFT_KEYWORD"] - if sys.version_info >= (3, 12): __all__ += ["EXCLAMATION", "FSTRING_END", "FSTRING_MIDDLE", "FSTRING_START", "EXACT_TOKEN_TYPES"] diff --git a/mypy/typeshed/stdlib/tomllib.pyi b/mypy/typeshed/stdlib/tomllib.pyi index 4ff4097f8313a..7f6df1d9380ca 100644 --- a/mypy/typeshed/stdlib/tomllib.pyi +++ b/mypy/typeshed/stdlib/tomllib.pyi @@ -13,6 +13,7 @@ if sys.version_info >= (3, 14): pos: int lineno: int colno: int + @overload def __init__(self, msg: str, doc: str, pos: int) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/trace.pyi b/mypy/typeshed/stdlib/trace.pyi index 7e7cc1e9ac54a..708233efe3e6a 100644 --- a/mypy/typeshed/stdlib/trace.pyi +++ b/mypy/typeshed/stdlib/trace.pyi @@ -2,8 +2,7 @@ import sys import types from _typeshed import Incomplete, StrPath, TraceFunction from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import Any, TypeVar -from typing_extensions import ParamSpec, TypeAlias +from typing import Any, ParamSpec, TypeAlias, TypeVar __all__ = ["Trace", "CoverageResults"] diff --git a/mypy/typeshed/stdlib/traceback.pyi b/mypy/typeshed/stdlib/traceback.pyi index f9d88f25afd97..e5b410afdcbc0 100644 --- a/mypy/typeshed/stdlib/traceback.pyi +++ b/mypy/typeshed/stdlib/traceback.pyi @@ -2,8 +2,8 @@ import sys from _typeshed import SupportsWrite, Unused from collections.abc import Generator, Iterable, Iterator, Mapping from types import FrameType, TracebackType -from typing import Any, ClassVar, Literal, SupportsIndex, overload -from typing_extensions import Self, TypeAlias, deprecated +from typing import Any, ClassVar, Literal, SupportsIndex, TypeAlias, overload +from typing_extensions import Self, deprecated __all__ = [ "extract_stack", @@ -34,49 +34,32 @@ _FrameSummaryTuple: TypeAlias = tuple[str, int, str, str | None] def print_tb(tb: TracebackType | None, limit: int | None = None, file: SupportsWrite[str] | None = None) -> None: ... -if sys.version_info >= (3, 10): - @overload - def print_exception( - exc: type[BaseException] | None, - /, - value: BaseException | None = ..., - tb: TracebackType | None = ..., - limit: int | None = None, - file: SupportsWrite[str] | None = None, - chain: bool = True, - ) -> None: ... - @overload - def print_exception( - exc: BaseException, /, *, limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True - ) -> None: ... - @overload - def format_exception( - exc: type[BaseException] | None, - /, - value: BaseException | None = ..., - tb: TracebackType | None = ..., - limit: int | None = None, - chain: bool = True, - ) -> list[str]: ... - @overload - def format_exception(exc: BaseException, /, *, limit: int | None = None, chain: bool = True) -> list[str]: ... +@overload +def print_exception( + exc: type[BaseException] | None, + /, + value: BaseException | None = ..., + tb: TracebackType | None = ..., + limit: int | None = None, + file: SupportsWrite[str] | None = None, + chain: bool = True, +) -> None: ... +@overload +def print_exception( + exc: BaseException, /, *, limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True +) -> None: ... -else: - def print_exception( - etype: type[BaseException] | None, - value: BaseException | None, - tb: TracebackType | None, - limit: int | None = None, - file: SupportsWrite[str] | None = None, - chain: bool = True, - ) -> None: ... - def format_exception( - etype: type[BaseException] | None, - value: BaseException | None, - tb: TracebackType | None, - limit: int | None = None, - chain: bool = True, - ) -> list[str]: ... +@overload +def format_exception( + exc: type[BaseException] | None, + /, + value: BaseException | None = ..., + tb: TracebackType | None = ..., + limit: int | None = None, + chain: bool = True, +) -> list[str]: ... +@overload +def format_exception(exc: BaseException, /, *, limit: int | None = None, chain: bool = True) -> list[str]: ... def print_exc(limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True) -> None: ... def print_last(limit: int | None = None, file: SupportsWrite[str] | None = None, chain: bool = True) -> None: ... @@ -91,16 +74,12 @@ if sys.version_info >= (3, 13): def format_exception_only(exc: BaseException | None, /, *, show_group: bool = False) -> list[str]: ... @overload def format_exception_only(exc: Unused, /, value: BaseException | None, *, show_group: bool = False) -> list[str]: ... - -elif sys.version_info >= (3, 10): +else: @overload def format_exception_only(exc: BaseException | None, /) -> list[str]: ... @overload def format_exception_only(exc: Unused, /, value: BaseException | None) -> list[str]: ... -else: - def format_exception_only(etype: type[BaseException] | None, value: BaseException | None) -> list[str]: ... - def format_exc(limit: int | None = None, chain: bool = True) -> str: ... def format_tb(tb: TracebackType | None, limit: int | None = None) -> list[str]: ... def format_stack(f: FrameType | None = None, limit: int | None = None) -> list[str]: ... @@ -126,12 +105,10 @@ class TracebackException: # These fields only exist for `SyntaxError`s, but there is no way to express that in the type system. filename: str lineno: str | None - if sys.version_info >= (3, 10): - end_lineno: str | None + end_lineno: str | None text: str offset: int - if sys.version_info >= (3, 10): - end_offset: int | None + end_offset: int | None msg: str if sys.version_info >= (3, 13): @@ -173,19 +150,6 @@ class TracebackException: max_group_depth: int = 10, _seen: set[int] | None = None, ) -> None: ... - elif sys.version_info >= (3, 10): - def __init__( - self, - exc_type: type[BaseException], - exc_value: BaseException, - exc_traceback: TracebackType | None, - *, - limit: int | None = None, - lookup_lines: bool = True, - capture_locals: bool = False, - compact: bool = False, - _seen: set[int] | None = None, - ) -> None: ... else: def __init__( self, @@ -196,6 +160,7 @@ class TracebackException: limit: int | None = None, lookup_lines: bool = True, capture_locals: bool = False, + compact: bool = False, _seen: set[int] | None = None, ) -> None: ... @@ -212,7 +177,7 @@ class TracebackException: max_group_width: int = 15, max_group_depth: int = 10, ) -> Self: ... - elif sys.version_info >= (3, 10): + else: @classmethod def from_exception( cls, @@ -223,11 +188,6 @@ class TracebackException: capture_locals: bool = False, compact: bool = False, ) -> Self: ... - else: - @classmethod - def from_exception( - cls, exc: BaseException, *, limit: int | None = None, lookup_lines: bool = True, capture_locals: bool = False - ) -> Self: ... def __eq__(self, other: object) -> bool: ... __hash__: ClassVar[None] # type: ignore[assignment] @@ -296,6 +256,7 @@ class FrameSummary: locals: dict[str, str] | None @property def line(self) -> str | None: ... + @overload def __getitem__(self, pos: Literal[0]) -> str: ... @overload @@ -308,6 +269,7 @@ class FrameSummary: def __getitem__(self, pos: SupportsIndex) -> Any: ... @overload def __getitem__(self, pos: slice[SupportsIndex | None]) -> tuple[Any, ...]: ... + def __iter__(self) -> Iterator[Any]: ... def __eq__(self, other: object) -> bool: ... def __len__(self) -> Literal[4]: ... diff --git a/mypy/typeshed/stdlib/tracemalloc.pyi b/mypy/typeshed/stdlib/tracemalloc.pyi index 2a7ee0051af9c..e56aa19240fda 100644 --- a/mypy/typeshed/stdlib/tracemalloc.pyi +++ b/mypy/typeshed/stdlib/tracemalloc.pyi @@ -1,8 +1,7 @@ import sys from _tracemalloc import * from collections.abc import Sequence -from typing import Any, SupportsIndex, overload -from typing_extensions import TypeAlias +from typing import Any, SupportsIndex, TypeAlias, overload def get_object_traceback(obj: object) -> Traceback | None: ... def take_snapshot() -> Snapshot: ... @@ -92,10 +91,12 @@ class Traceback(Sequence[Frame]): def total_nframe(self) -> int | None: ... def __init__(self, frames: Sequence[_FrameTuple], total_nframe: int | None = None) -> None: ... def format(self, limit: int | None = None, most_recent_first: bool = False) -> list[str]: ... + @overload def __getitem__(self, index: SupportsIndex) -> Frame: ... @overload def __getitem__(self, index: slice[SupportsIndex | None]) -> Sequence[Frame]: ... + def __contains__(self, frame: Frame) -> bool: ... # type: ignore[override] def __len__(self) -> int: ... def __eq__(self, other: object) -> bool: ... diff --git a/mypy/typeshed/stdlib/tty.pyi b/mypy/typeshed/stdlib/tty.pyi index ca3f0013b20ec..a0478335a3f32 100644 --- a/mypy/typeshed/stdlib/tty.pyi +++ b/mypy/typeshed/stdlib/tty.pyi @@ -1,7 +1,6 @@ import sys import termios -from typing import IO, Final -from typing_extensions import TypeAlias +from typing import IO, Final, TypeAlias if sys.platform != "win32": __all__ = ["setraw", "setcbreak"] diff --git a/mypy/typeshed/stdlib/turtle.pyi b/mypy/typeshed/stdlib/turtle.pyi index b5f536d0e28e5..b1f5198a89c06 100644 --- a/mypy/typeshed/stdlib/turtle.pyi +++ b/mypy/typeshed/stdlib/turtle.pyi @@ -3,8 +3,8 @@ from _typeshed import StrPath from collections.abc import Callable, Generator, Sequence from contextlib import contextmanager from tkinter import Canvas, Frame, Misc, PhotoImage, Scrollbar -from typing import Any, ClassVar, Literal, TypedDict, overload, type_check_only -from typing_extensions import Self, TypeAlias, deprecated, disjoint_base +from typing import Any, ClassVar, Literal, TypeAlias, TypedDict, overload, type_check_only +from typing_extensions import Self, deprecated, disjoint_base __all__ = [ "ScrolledCanvas", @@ -167,10 +167,12 @@ if sys.version_info >= (3, 12): class Vec2D(tuple[float, float]): def __new__(cls, x: float, y: float) -> Self: ... def __add__(self, other: tuple[float, float]) -> Vec2D: ... # type: ignore[override] + @overload # type: ignore[override] def __mul__(self, other: Vec2D) -> float: ... @overload def __mul__(self, other: float) -> Vec2D: ... + def __rmul__(self, other: float) -> Vec2D: ... # type: ignore[override] def __sub__(self, other: tuple[float, float]) -> Vec2D: ... def __neg__(self) -> Vec2D: ... @@ -182,10 +184,12 @@ else: class Vec2D(tuple[float, float]): def __new__(cls, x: float, y: float) -> Self: ... def __add__(self, other: tuple[float, float]) -> Vec2D: ... # type: ignore[override] + @overload # type: ignore[override] def __mul__(self, other: Vec2D) -> float: ... @overload def __mul__(self, other: float) -> Vec2D: ... + def __rmul__(self, other: float) -> Vec2D: ... # type: ignore[override] def __sub__(self, other: tuple[float, float]) -> Vec2D: ... def __neg__(self) -> Vec2D: ... @@ -231,32 +235,40 @@ class TurtleScreen(TurtleScreenBase): self, cv: Canvas, mode: Literal["standard", "logo", "world"] = "standard", colormode: float = 1.0, delay: int = 10 ) -> None: ... def clear(self) -> None: ... + @overload def mode(self, mode: None = None) -> str: ... @overload def mode(self, mode: Literal["standard", "logo", "world"]) -> None: ... + def setworldcoordinates(self, llx: float, lly: float, urx: float, ury: float) -> None: ... def register_shape(self, name: str, shape: _PolygonCoords | Shape | None = None) -> None: ... + @overload def colormode(self, cmode: None = None) -> float: ... @overload def colormode(self, cmode: float) -> None: ... + def reset(self) -> None: ... def turtles(self) -> list[Turtle]: ... + @overload def bgcolor(self) -> _AnyColor: ... @overload def bgcolor(self, color: _Color) -> None: ... @overload def bgcolor(self, r: float, g: float, b: float) -> None: ... + @overload def tracer(self, n: None = None) -> int: ... @overload def tracer(self, n: int, delay: int | None = None) -> None: ... + @overload def delay(self, delay: None = None) -> int: ... @overload def delay(self, delay: int) -> None: ... + if sys.version_info >= (3, 14): @contextmanager def no_animation(self) -> Generator[None]: ... @@ -270,15 +282,18 @@ class TurtleScreen(TurtleScreenBase): def onkey(self, fun: Callable[[], object], key: str) -> None: ... def listen(self, xdummy: float | None = None, ydummy: float | None = None) -> None: ... def ontimer(self, fun: Callable[[], object], t: int = 0) -> None: ... + @overload def bgpic(self, picname: None = None) -> str: ... @overload def bgpic(self, picname: str) -> None: ... + @overload def screensize(self, canvwidth: None = None, canvheight: None = None, bg: None = None) -> tuple[int, int]: ... # Looks like if self.cv is not a ScrolledCanvas, this could return a tuple as well @overload def screensize(self, canvwidth: int, canvheight: int, bg: _Color | None = None) -> None: ... + if sys.version_info >= (3, 14): def save(self, filename: StrPath, *, overwrite: bool = False) -> None: ... onscreenclick = onclick @@ -307,21 +322,26 @@ class TNavigator: def pos(self) -> Vec2D: ... def xcor(self) -> float: ... def ycor(self) -> float: ... + @overload def goto(self, x: tuple[float, float], y: None = None) -> None: ... @overload def goto(self, x: float, y: float) -> None: ... + def home(self) -> None: ... def setx(self, x: float) -> None: ... def sety(self, y: float) -> None: ... + @overload def distance(self, x: TNavigator | tuple[float, float], y: None = None) -> float: ... @overload def distance(self, x: float, y: float) -> float: ... + @overload def towards(self, x: TNavigator | tuple[float, float], y: None = None) -> float: ... @overload def towards(self, x: float, y: float) -> float: ... + def heading(self) -> float: ... def setheading(self, to_angle: float) -> None: ... def circle(self, radius: float, extent: float | None = None, steps: int | None = None) -> None: ... @@ -338,33 +358,40 @@ class TNavigator: class TPen: def __init__(self, resizemode: Literal["auto", "user", "noresize"] = "noresize") -> None: ... + @overload def resizemode(self, rmode: None = None) -> str: ... @overload def resizemode(self, rmode: Literal["auto", "user", "noresize"]) -> None: ... + @overload def pensize(self, width: None = None) -> int: ... @overload def pensize(self, width: int) -> None: ... + def penup(self) -> None: ... def pendown(self) -> None: ... def isdown(self) -> bool: ... + @overload def speed(self, speed: None = None) -> int: ... @overload def speed(self, speed: _Speed) -> None: ... + @overload def pencolor(self) -> _AnyColor: ... @overload def pencolor(self, color: _Color) -> None: ... @overload def pencolor(self, r: float, g: float, b: float) -> None: ... + @overload def fillcolor(self) -> _AnyColor: ... @overload def fillcolor(self, color: _Color) -> None: ... @overload def fillcolor(self, r: float, g: float, b: float) -> None: ... + @overload def color(self) -> tuple[_AnyColor, _AnyColor]: ... @overload @@ -373,12 +400,14 @@ class TPen: def color(self, r: float, g: float, b: float) -> None: ... @overload def color(self, color1: _Color, color2: _Color) -> None: ... + if sys.version_info >= (3, 12): def teleport(self, x: float | None = None, y: float | None = None, *, fill_gap: bool = False) -> None: ... def showturtle(self) -> None: ... def hideturtle(self) -> None: ... def isvisible(self) -> bool: ... + # Note: signatures 1 and 2 overlap unsafely when no arguments are provided @overload def pen(self) -> _PenState: ... @@ -398,6 +427,7 @@ class TPen: outline: int = ..., tilt: float = ..., ) -> None: ... + width = pensize up = penup pu = penup @@ -421,10 +451,12 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def undobufferentries(self) -> int: ... def clear(self) -> None: ... def clone(self) -> Self: ... + @overload def shape(self, name: None = None) -> str: ... @overload def shape(self, name: str) -> None: ... + # Unsafely overlaps when no arguments are provided @overload def shapesize(self) -> tuple[float, float, float]: ... @@ -432,10 +464,12 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def shapesize( self, stretch_wid: float | None = None, stretch_len: float | None = None, outline: float | None = None ) -> None: ... + @overload def shearfactor(self, shear: None = None) -> float: ... @overload def shearfactor(self, shear: float) -> None: ... + # Unsafely overlaps when no arguments are provided @overload def shapetransform(self) -> tuple[float, float, float, float]: ... @@ -443,6 +477,7 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def shapetransform( self, t11: float | None = None, t12: float | None = None, t21: float | None = None, t22: float | None = None ) -> None: ... + def get_shapepoly(self) -> _PolygonCoords | None: ... if sys.version_info < (3, 13): @@ -453,6 +488,7 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def tiltangle(self, angle: None = None) -> float: ... @overload def tiltangle(self, angle: float) -> None: ... + def tilt(self, angle: float) -> None: ... # Can return either 'int' or Tuple[int, ...] based on if the stamp is # a compound stamp or not. So, as per the "no Union return" policy, @@ -467,12 +503,14 @@ class RawTurtle(TPen, TNavigator): # type: ignore[misc] # Conflicting methods def begin_fill(self) -> None: ... def end_fill(self) -> None: ... + @overload def dot(self, size: int | _Color | None = None) -> None: ... @overload def dot(self, size: int | None, color: _Color, /) -> None: ... @overload def dot(self, size: int | None, r: float, g: float, b: float, /) -> None: ... + def write( self, arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal") ) -> None: ... @@ -525,28 +563,35 @@ def numinput( # Functions copied from TurtleScreen: def clear() -> None: ... + @overload def mode(mode: None = None) -> str: ... @overload def mode(mode: Literal["standard", "logo", "world"]) -> None: ... + def setworldcoordinates(llx: float, lly: float, urx: float, ury: float) -> None: ... def register_shape(name: str, shape: _PolygonCoords | Shape | None = None) -> None: ... + @overload def colormode(cmode: None = None) -> float: ... @overload def colormode(cmode: float) -> None: ... + def reset() -> None: ... def turtles() -> list[Turtle]: ... + @overload def bgcolor() -> _AnyColor: ... @overload def bgcolor(color: _Color) -> None: ... @overload def bgcolor(r: float, g: float, b: float) -> None: ... + @overload def tracer(n: None = None) -> int: ... @overload def tracer(n: int, delay: int | None = None) -> None: ... + @overload def delay(delay: None = None) -> int: ... @overload @@ -565,10 +610,12 @@ def onclick(fun: Callable[[float, float], object], btn: int = 1, add: bool | Non def onkey(fun: Callable[[], object], key: str) -> None: ... def listen(xdummy: float | None = None, ydummy: float | None = None) -> None: ... def ontimer(fun: Callable[[], object], t: int = 0) -> None: ... + @overload def bgpic(picname: None = None) -> str: ... @overload def bgpic(picname: str) -> None: ... + @overload def screensize(canvwidth: None = None, canvheight: None = None, bg: None = None) -> tuple[int, int]: ... @overload @@ -605,21 +652,26 @@ def left(angle: float) -> None: ... def pos() -> Vec2D: ... def xcor() -> float: ... def ycor() -> float: ... + @overload def goto(x: tuple[float, float], y: None = None) -> None: ... @overload def goto(x: float, y: float) -> None: ... + def home() -> None: ... def setx(x: float) -> None: ... def sety(y: float) -> None: ... + @overload def distance(x: TNavigator | tuple[float, float], y: None = None) -> float: ... @overload def distance(x: float, y: float) -> float: ... + @overload def towards(x: TNavigator | tuple[float, float], y: None = None) -> float: ... @overload def towards(x: float, y: float) -> float: ... + def heading() -> float: ... def setheading(to_angle: float) -> None: ... def circle(radius: float, extent: float | None = None, steps: int | None = None) -> None: ... @@ -639,29 +691,35 @@ seth = setheading def resizemode(rmode: None = None) -> str: ... @overload def resizemode(rmode: Literal["auto", "user", "noresize"]) -> None: ... + @overload def pensize(width: None = None) -> int: ... @overload def pensize(width: int) -> None: ... + def penup() -> None: ... def pendown() -> None: ... def isdown() -> bool: ... + @overload def speed(speed: None = None) -> int: ... @overload def speed(speed: _Speed) -> None: ... + @overload def pencolor() -> _AnyColor: ... @overload def pencolor(color: _Color) -> None: ... @overload def pencolor(r: float, g: float, b: float) -> None: ... + @overload def fillcolor() -> _AnyColor: ... @overload def fillcolor(color: _Color) -> None: ... @overload def fillcolor(r: float, g: float, b: float) -> None: ... + @overload def color() -> tuple[_AnyColor, _AnyColor]: ... @overload @@ -670,6 +728,7 @@ def color(color: _Color) -> None: ... def color(r: float, g: float, b: float) -> None: ... @overload def color(color1: _Color, color2: _Color) -> None: ... + def showturtle() -> None: ... def hideturtle() -> None: ... def isvisible() -> bool: ... @@ -705,6 +764,7 @@ ht = hideturtle def setundobuffer(size: int | None) -> None: ... def undobufferentries() -> int: ... + @overload def shape(name: None = None) -> str: ... @overload @@ -718,6 +778,7 @@ if sys.version_info >= (3, 12): def shapesize() -> tuple[float, float, float]: ... @overload def shapesize(stretch_wid: float | None = None, stretch_len: float | None = None, outline: float | None = None) -> None: ... + @overload def shearfactor(shear: None = None) -> float: ... @overload @@ -730,6 +791,7 @@ def shapetransform() -> tuple[float, float, float, float]: ... def shapetransform( t11: float | None = None, t12: float | None = None, t21: float | None = None, t22: float | None = None ) -> None: ... + def get_shapepoly() -> _PolygonCoords | None: ... if sys.version_info < (3, 13): @@ -740,6 +802,7 @@ if sys.version_info < (3, 13): def tiltangle(angle: None = None) -> float: ... @overload def tiltangle(angle: float) -> None: ... + def tilt(angle: float) -> None: ... # Can return either 'int' or Tuple[int, ...] based on if the stamp is @@ -756,12 +819,14 @@ if sys.version_info >= (3, 14): def begin_fill() -> None: ... def end_fill() -> None: ... + @overload def dot(size: int | _Color | None = None) -> None: ... @overload def dot(size: int | None, color: _Color, /) -> None: ... @overload def dot(size: int | None, r: float, g: float, b: float, /) -> None: ... + def write(arg: object, move: bool = False, align: str = "left", font: tuple[str, int, str] = ("Arial", 8, "normal")) -> None: ... if sys.version_info >= (3, 14): diff --git a/mypy/typeshed/stdlib/types.pyi b/mypy/typeshed/stdlib/types.pyi index e26c9447d2f75..68b6b3fbe41d7 100644 --- a/mypy/typeshed/stdlib/types.pyi +++ b/mypy/typeshed/stdlib/types.pyi @@ -12,12 +12,13 @@ from collections.abc import ( Iterator, KeysView, Mapping, + MutableMapping, MutableSequence, ValuesView, ) from importlib.machinery import ModuleSpec -from typing import Any, ClassVar, Literal, TypeVar, final, overload -from typing_extensions import ParamSpec, Self, TypeAliasType, TypeVarTuple, deprecated, disjoint_base +from typing import Any, ClassVar, Literal, ParamSpec, TypeVar, final, overload +from typing_extensions import Self, TypeAliasType, TypeVarTuple, deprecated, disjoint_base if sys.version_info >= (3, 14): from _typeshed import AnnotateFunc @@ -50,17 +51,21 @@ __all__ = [ "resolve_bases", "CellType", "GenericAlias", + "EllipsisType", + "NoneType", + "NotImplementedType", + "UnionType", ] -if sys.version_info >= (3, 10): - __all__ += ["EllipsisType", "NoneType", "NotImplementedType", "UnionType"] - if sys.version_info >= (3, 12): __all__ += ["get_original_bases"] if sys.version_info >= (3, 13): __all__ += ["CapsuleType"] +if sys.version_info >= (3, 15): + __all__ += ["FrameLocalsProxyType", "LazyImportType"] + # Note, all classes "defined" here require special handling. _T1 = TypeVar("_T1") @@ -84,9 +89,8 @@ class FunctionType: if sys.version_info >= (3, 14): __annotate__: AnnotateFunc | None __kwdefaults__: dict[str, Any] | None - if sys.version_info >= (3, 10): - @property - def __builtins__(self) -> dict[str, Any]: ... + @property + def __builtins__(self) -> dict[str, Any]: ... if sys.version_info >= (3, 12): __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] @@ -112,6 +116,7 @@ class FunctionType: ) -> Self: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + @overload def __get__(self, instance: None, owner: type, /) -> FunctionType: ... @overload @@ -149,22 +154,18 @@ class CodeType: def co_name(self) -> str: ... @property def co_firstlineno(self) -> int: ... - if sys.version_info >= (3, 10): + if sys.version_info < (3, 15): @property @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `CodeType.co_lines()` instead.") def co_lnotab(self) -> bytes: ... - else: - @property - def co_lnotab(self) -> bytes: ... @property def co_freevars(self) -> tuple[str, ...]: ... @property def co_cellvars(self) -> tuple[str, ...]: ... - if sys.version_info >= (3, 10): - @property - def co_linetable(self) -> bytes: ... - def co_lines(self) -> Iterator[tuple[int, int, int | None]]: ... + @property + def co_linetable(self) -> bytes: ... + def co_lines(self) -> Iterator[tuple[int, int, int | None]]: ... if sys.version_info >= (3, 11): @property def co_exceptiontable(self) -> bytes: ... @@ -197,27 +198,6 @@ class CodeType: cellvars: tuple[str, ...] = ..., /, ) -> Self: ... - elif sys.version_info >= (3, 10): - def __new__( - cls, - argcount: int, - posonlyargcount: int, - kwonlyargcount: int, - nlocals: int, - stacksize: int, - flags: int, - codestring: bytes, - constants: tuple[object, ...], - names: tuple[str, ...], - varnames: tuple[str, ...], - filename: str, - name: str, - firstlineno: int, - linetable: bytes, - freevars: tuple[str, ...] = ..., - cellvars: tuple[str, ...] = ..., - /, - ) -> Self: ... else: def __new__( cls, @@ -234,7 +214,7 @@ class CodeType: filename: str, name: str, firstlineno: int, - lnotab: bytes, + linetable: bytes, freevars: tuple[str, ...] = ..., cellvars: tuple[str, ...] = ..., /, @@ -262,27 +242,6 @@ class CodeType: co_linetable: bytes = ..., co_exceptiontable: bytes = ..., ) -> Self: ... - elif sys.version_info >= (3, 10): - def replace( - self, - *, - co_argcount: int = -1, - co_posonlyargcount: int = -1, - co_kwonlyargcount: int = -1, - co_nlocals: int = -1, - co_stacksize: int = -1, - co_flags: int = -1, - co_firstlineno: int = -1, - co_code: bytes = ..., - co_consts: tuple[object, ...] = ..., - co_names: tuple[str, ...] = ..., - co_varnames: tuple[str, ...] = ..., - co_freevars: tuple[str, ...] = ..., - co_cellvars: tuple[str, ...] = ..., - co_filename: str = ..., - co_name: str = ..., - co_linetable: bytes = ..., - ) -> Self: ... else: def replace( self, @@ -302,7 +261,7 @@ class CodeType: co_cellvars: tuple[str, ...] = ..., co_filename: str = ..., co_name: str = ..., - co_lnotab: bytes = ..., + co_linetable: bytes = ..., ) -> Self: ... if sys.version_info >= (3, 13): @@ -320,12 +279,14 @@ class MappingProxyType(Mapping[_KT_co, _VT_co]): # type: ignore[type-var] # py def keys(self) -> KeysView[_KT_co]: ... def values(self) -> ValuesView[_VT_co]: ... def items(self) -> ItemsView[_KT_co, _VT_co]: ... + @overload def get(self, key: _KT_co, /) -> _VT_co | None: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter @overload def get(self, key: _KT_co, default: _VT_co, /) -> _VT_co: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter @overload def get(self, key: _KT_co, default: _T2, /) -> _VT_co | _T2: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... def __reversed__(self) -> Iterator[_KT_co]: ... def __or__(self, value: Mapping[_T1, _T2], /) -> dict[_KT_co | _T1, _VT_co | _T2]: ... @@ -408,17 +369,22 @@ class GeneratorType(Generator[_YieldT_co, _SendT_contra, _ReturnT_co]): if sys.version_info >= (3, 11): @property def gi_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def gi_state(self) -> Literal["GEN_CREATED", "GEN_SUSPENDED", "GEN_RUNNING", "GEN_CLOSED"]: ... __name__: str __qualname__: str def __iter__(self) -> Self: ... def __next__(self) -> _YieldT_co: ... def send(self, arg: _SendT_contra, /) -> _YieldT_co: ... + @overload def throw( self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / ) -> _YieldT_co: ... @overload def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... + if sys.version_info >= (3, 13): def __class_getitem__(cls, item: Any, /) -> Any: ... @@ -437,16 +403,21 @@ class AsyncGeneratorType(AsyncGenerator[_YieldT_co, _SendT_contra]): if sys.version_info >= (3, 12): @property def ag_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def ag_state(self) -> Literal["AGEN_CREATED", "AGEN_SUSPENDED", "AGEN_RUNNING", "AGEN_CLOSED"]: ... def __aiter__(self) -> Self: ... def __anext__(self) -> Coroutine[Any, Any, _YieldT_co]: ... def asend(self, val: _SendT_contra, /) -> Coroutine[Any, Any, _YieldT_co]: ... + @overload async def athrow( self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / ) -> _YieldT_co: ... @overload async def athrow(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... + def aclose(self) -> Coroutine[Any, Any, None]: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -471,16 +442,21 @@ class CoroutineType(Coroutine[_YieldT_co, _SendT_nd_contra, _ReturnT_nd_co]): if sys.version_info >= (3, 11): @property def cr_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def cr_state(self) -> Literal["CORO_CREATED", "CORO_SUSPENDED", "CORO_RUNNING", "CORO_CLOSED"]: ... def close(self) -> None: ... def __await__(self) -> Generator[Any, None, _ReturnT_nd_co]: ... def send(self, arg: _SendT_nd_contra, /) -> _YieldT_co: ... + @overload def throw( self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ..., / ) -> _YieldT_co: ... @overload def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = ..., /) -> _YieldT_co: ... + if sys.version_info >= (3, 13): def __class_getitem__(cls, item: Any, /) -> Any: ... @@ -600,8 +576,14 @@ class FrameType: # An `int | None` annotation here causes too many false-positive errors, so applying `int | Any`. @property def f_lineno(self) -> int | MaybeNone: ... - @property - def f_locals(self) -> dict[str, Any]: ... + + if sys.version_info >= (3, 15): + @property + def f_locals(self) -> FrameLocalsProxyType | dict[str, Any]: ... + else: + @property + def f_locals(self) -> dict[str, Any]: ... + f_trace: Callable[[FrameType, str, Any], Any] | None f_trace_lines: bool f_trace_opcodes: bool @@ -610,6 +592,28 @@ class FrameType: @property def f_generator(self) -> GeneratorType[Any, Any, Any] | CoroutineType[Any, Any, Any] | None: ... +if sys.version_info >= (3, 15): + @final + class FrameLocalsProxyType(MutableMapping[str, Any]): + def __new__(cls, frame: FrameType, /) -> Self: ... + def __getitem__(self, key: str, /) -> Any: ... + def __setitem__(self, key: str, value: Any, /) -> None: ... + def __delitem__(self, key: str, /) -> None: ... + def __iter__(self) -> Iterator[str]: ... + def __len__(self) -> int: ... + def __contains__(self, key: object, /) -> bool: ... + def __reversed__(self) -> Iterator[str]: ... + def copy(self) -> dict[str, Any]: ... + def pop(self, key: str, default: Any = ..., /) -> Any: ... + def setdefault(self, key: str, default: Any = ..., /) -> Any: ... + def update(self, object: SupportsKeysAndGetItem[str, Any] | Iterable[tuple[str, Any]], /) -> None: ... # type: ignore[override] + + @final + class LazyImportType: + @property + def __name__(self) -> str: ... + def resolve(self) -> Any: ... + @final class GetSetDescriptorType: @property @@ -679,6 +683,7 @@ _P = ParamSpec("_P") def coroutine(func: Callable[_P, Generator[Any, Any, _R]]) -> Callable[_P, Awaitable[_R]]: ... @overload def coroutine(func: _Fn) -> _Fn: ... + @disjoint_base class GenericAlias: @property @@ -697,43 +702,44 @@ class GenericAlias: def __unpacked__(self) -> bool: ... @property def __typing_unpacked_tuple_args__(self) -> tuple[Any, ...] | None: ... - if sys.version_info >= (3, 10): - def __or__(self, value: Any, /) -> UnionType: ... - def __ror__(self, value: Any, /) -> UnionType: ... + + # `other` can be any legal form for unions. + # `list[int] | list[int]` creates a `GenericAlias` instance, not a `UnionType` instance + def __or__(self, value: Any, /) -> UnionType | GenericAlias: ... + def __ror__(self, value: Any, /) -> UnionType | GenericAlias: ... # GenericAlias delegates attr access to `__origin__` def __getattr__(self, name: str) -> Any: ... -if sys.version_info >= (3, 10): - @final - class NoneType: - def __bool__(self) -> Literal[False]: ... +@final +class NoneType: + def __bool__(self) -> Literal[False]: ... - @final - class EllipsisType: ... +@final +class EllipsisType: ... - @final - class NotImplementedType(Any): ... +@final +class NotImplementedType(Any): ... - @final - class UnionType: - @property - def __args__(self) -> tuple[Any, ...]: ... - @property - def __parameters__(self) -> tuple[Any, ...]: ... - # `(int | str) | Literal["foo"]` returns a generic alias to an instance of `_SpecialForm` (`Union`). - # Normally we'd express this using the return type of `_SpecialForm.__ror__`, - # but because `UnionType.__or__` accepts `Any`, type checkers will use - # the return type of `UnionType.__or__` to infer the result of this operation - # rather than `_SpecialForm.__ror__`. To mitigate this, we use `| Any` - # in the return type of `UnionType.__(r)or__`. - def __or__(self, value: Any, /) -> UnionType | Any: ... - def __ror__(self, value: Any, /) -> UnionType | Any: ... - def __eq__(self, value: object, /) -> bool: ... - def __hash__(self) -> int: ... - # you can only subscript a `UnionType` instance if at least one of the elements - # in the union is a generic alias instance that has a non-empty `__parameters__` - def __getitem__(self, parameters: Any, /) -> object: ... +@final +class UnionType: + @property + def __args__(self) -> tuple[Any, ...]: ... + @property + def __parameters__(self) -> tuple[Any, ...]: ... + # `(int | str) | Literal["foo"]` returns a generic alias to an instance of `_SpecialForm` (`Union`). + # Normally we'd express this using the return type of `_SpecialForm.__ror__`, + # but because `UnionType.__or__` accepts `Any`, type checkers will use + # the return type of `UnionType.__or__` to infer the result of this operation + # rather than `_SpecialForm.__ror__`. To mitigate this, we use `| Any` + # in the return type of `UnionType.__(r)or__`. + def __or__(self, value: Any, /) -> UnionType | Any: ... + def __ror__(self, value: Any, /) -> UnionType | Any: ... + def __eq__(self, value: object, /) -> bool: ... + def __hash__(self) -> int: ... + # you can only subscript a `UnionType` instance if at least one of the elements + # in the union is a generic alias instance that has a non-empty `__parameters__` + def __getitem__(self, parameters: Any, /) -> object: ... if sys.version_info >= (3, 13): @final diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index 0bced03866439..811a77df84b76 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -18,18 +18,16 @@ from types import ( MethodWrapperType, ModuleType, TracebackType, + UnionType, WrapperDescriptorType, ) -from typing_extensions import Never as _Never, ParamSpec as _ParamSpec, deprecated +from typing_extensions import Never as _Never, deprecated if sys.version_info >= (3, 14): from _typeshed import EvaluateFunc from annotationlib import Format -if sys.version_info >= (3, 10): - from types import UnionType - __all__ = [ "AbstractSet", "Annotated", @@ -41,11 +39,11 @@ __all__ = [ "AsyncIterator", "Awaitable", "BinaryIO", - "ByteString", "Callable", "ChainMap", "ClassVar", "Collection", + "Concatenate", "Container", "ContextManager", "Coroutine", @@ -77,6 +75,9 @@ __all__ = [ "NoReturn", "Optional", "OrderedDict", + "ParamSpec", + "ParamSpecArgs", + "ParamSpecKwargs", "Pattern", "Protocol", "Reversible", @@ -94,6 +95,8 @@ __all__ = [ "TextIO", "Tuple", "Type", + "TypeAlias", + "TypeGuard", "TypeVar", "TypedDict", "Union", @@ -104,17 +107,20 @@ __all__ = [ "get_args", "get_origin", "get_type_hints", + "is_typeddict", "no_type_check", - "no_type_check_decorator", "overload", "runtime_checkable", ] +if sys.version_info < (3, 15): + __all__ += ["ByteString", "no_type_check_decorator"] + if sys.version_info >= (3, 14): __all__ += ["evaluate_forward_ref"] -if sys.version_info >= (3, 10): - __all__ += ["Concatenate", "ParamSpec", "ParamSpecArgs", "ParamSpecKwargs", "TypeAlias", "TypeGuard", "is_typeddict"] +if sys.version_info >= (3, 15): + __all__ += ["NoExtraItems", "TypeForm", "disjoint_base"] if sys.version_info >= (3, 11): __all__ += [ @@ -149,6 +155,7 @@ class _Final: __slots__ = ("__weakref__",) def final(f: _T) -> _T: ... + @final class TypeVar: @property @@ -206,9 +213,9 @@ class TypeVar: covariant: bool = False, contravariant: bool = False, ) -> None: ... - if sys.version_info >= (3, 10): - def __or__(self, right: Any, /) -> _SpecialForm: ... # AnnotationForm - def __ror__(self, left: Any, /) -> _SpecialForm: ... # AnnotationForm + + def __or__(self, right: Any, /) -> _SpecialForm: ... # AnnotationForm + def __ror__(self, left: Any, /) -> _SpecialForm: ... # AnnotationForm if sys.version_info >= (3, 11): def __typing_subst__(self, arg: Any, /) -> Any: ... if sys.version_info >= (3, 13): @@ -227,9 +234,8 @@ class TypeVar: class _SpecialForm(_Final): __slots__ = ("_name", "__doc__", "_getitem") def __getitem__(self, parameters: Any) -> object: ... - if sys.version_info >= (3, 10): - def __or__(self, other: Any) -> _SpecialForm: ... - def __ror__(self, other: Any) -> _SpecialForm: ... + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... Union: _SpecialForm Protocol: _SpecialForm @@ -257,11 +263,31 @@ if sys.version_info >= (3, 11): class TypeVarTuple: @property def __name__(self) -> str: ... + if sys.version_info >= (3, 15): + @property + def __bound__(self) -> Any | None: ... # AnnotationForm + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + @property + def __infer_variance__(self) -> bool: ... if sys.version_info >= (3, 13): @property def __default__(self) -> Any: ... # AnnotationForm def has_default(self) -> bool: ... - if sys.version_info >= (3, 13): + if sys.version_info >= (3, 15): + def __new__( + cls, + name: str, + *, + bound: Any | None = None, # AnnotationForm + covariant: bool = False, + contravariant: bool = False, + default: Any = ..., # AnnotationForm + infer_variance: bool = False, + ) -> Self: ... + elif sys.version_info >= (3, 13): def __new__(cls, name: str, *, default: Any = ...) -> Self: ... # AnnotationForm elif sys.version_info >= (3, 12): def __new__(cls, name: str) -> Self: ... @@ -275,125 +301,111 @@ if sys.version_info >= (3, 11): @property def evaluate_default(self) -> EvaluateFunc | None: ... -if sys.version_info >= (3, 10): - @final - class ParamSpecArgs: - @property - def __origin__(self) -> ParamSpec: ... - if sys.version_info >= (3, 12): - def __new__(cls, origin: ParamSpec) -> Self: ... - else: - def __init__(self, origin: ParamSpec) -> None: ... - - def __eq__(self, other: object, /) -> bool: ... - __hash__: ClassVar[None] # type: ignore[assignment] +@final +class ParamSpecArgs: + @property + def __origin__(self) -> ParamSpec: ... + if sys.version_info >= (3, 12): + def __new__(cls, origin: ParamSpec) -> Self: ... + else: + def __init__(self, origin: ParamSpec) -> None: ... - @final - class ParamSpecKwargs: - @property - def __origin__(self) -> ParamSpec: ... - if sys.version_info >= (3, 12): - def __new__(cls, origin: ParamSpec) -> Self: ... - else: - def __init__(self, origin: ParamSpec) -> None: ... + def __eq__(self, other: object, /) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] - def __eq__(self, other: object, /) -> bool: ... - __hash__: ClassVar[None] # type: ignore[assignment] +@final +class ParamSpecKwargs: + @property + def __origin__(self) -> ParamSpec: ... + if sys.version_info >= (3, 12): + def __new__(cls, origin: ParamSpec) -> Self: ... + else: + def __init__(self, origin: ParamSpec) -> None: ... - @final - class ParamSpec: - @property - def __name__(self) -> str: ... - @property - def __bound__(self) -> Any | None: ... # AnnotationForm - @property - def __covariant__(self) -> bool: ... - @property - def __contravariant__(self) -> bool: ... - if sys.version_info >= (3, 12): - @property - def __infer_variance__(self) -> bool: ... - if sys.version_info >= (3, 13): - @property - def __default__(self) -> Any: ... # AnnotationForm - if sys.version_info >= (3, 13): - def __new__( - cls, - name: str, - *, - bound: Any | None = None, # AnnotationForm - contravariant: bool = False, - covariant: bool = False, - infer_variance: bool = False, - default: Any = ..., # AnnotationForm - ) -> Self: ... - elif sys.version_info >= (3, 12): - def __new__( - cls, - name: str, - *, - bound: Any | None = None, # AnnotationForm - contravariant: bool = False, - covariant: bool = False, - infer_variance: bool = False, - ) -> Self: ... - elif sys.version_info >= (3, 11): - def __new__( - cls, - name: str, - *, - bound: Any | None = None, # AnnotationForm - contravariant: bool = False, - covariant: bool = False, - ) -> Self: ... - else: - def __init__( - self, - name: str, - *, - bound: Any | None = None, # AnnotationForm - contravariant: bool = False, - covariant: bool = False, - ) -> None: ... + def __eq__(self, other: object, /) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] +@final +class ParamSpec: + @property + def __name__(self) -> str: ... + @property + def __bound__(self) -> Any | None: ... # AnnotationForm + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + if sys.version_info >= (3, 12): @property - def args(self) -> ParamSpecArgs: ... + def __infer_variance__(self) -> bool: ... + if sys.version_info >= (3, 13): @property - def kwargs(self) -> ParamSpecKwargs: ... - if sys.version_info >= (3, 11): - def __typing_subst__(self, arg: Any, /) -> Any: ... - def __typing_prepare_subst__(self, alias: Any, args: Any, /) -> tuple[Any, ...]: ... + def __default__(self) -> Any: ... # AnnotationForm + if sys.version_info >= (3, 13): + def __new__( + cls, + name: str, + *, + bound: Any | None = None, # AnnotationForm + contravariant: bool = False, + covariant: bool = False, + infer_variance: bool = False, + default: Any = ..., # AnnotationForm + ) -> Self: ... + elif sys.version_info >= (3, 12): + def __new__( + cls, + name: str, + *, + bound: Any | None = None, # AnnotationForm + contravariant: bool = False, + covariant: bool = False, + infer_variance: bool = False, + ) -> Self: ... + elif sys.version_info >= (3, 11): + def __new__( + cls, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False # AnnotationForm + ) -> Self: ... + else: + def __init__( + self, name: str, *, bound: Any | None = None, contravariant: bool = False, covariant: bool = False # AnnotationForm + ) -> None: ... - def __or__(self, right: Any, /) -> _SpecialForm: ... - def __ror__(self, left: Any, /) -> _SpecialForm: ... - if sys.version_info >= (3, 13): - def has_default(self) -> bool: ... - if sys.version_info >= (3, 14): - @property - def evaluate_default(self) -> EvaluateFunc | None: ... + @property + def args(self) -> ParamSpecArgs: ... + @property + def kwargs(self) -> ParamSpecKwargs: ... + if sys.version_info >= (3, 11): + def __typing_subst__(self, arg: Any, /) -> Any: ... + def __typing_prepare_subst__(self, alias: Any, args: Any, /) -> tuple[Any, ...]: ... - Concatenate: _SpecialForm - TypeAlias: _SpecialForm - TypeGuard: _SpecialForm + def __or__(self, right: Any, /) -> _SpecialForm: ... + def __ror__(self, left: Any, /) -> _SpecialForm: ... + if sys.version_info >= (3, 13): + def has_default(self) -> bool: ... + if sys.version_info >= (3, 14): + @property + def evaluate_default(self) -> EvaluateFunc | None: ... - class NewType: - def __init__(self, name: str, tp: Any) -> None: ... # AnnotationForm - if sys.version_info >= (3, 11): - @staticmethod - def __call__(x: _T, /) -> _T: ... - else: - def __call__(self, x: _T) -> _T: ... +Concatenate: _SpecialForm +TypeAlias: _SpecialForm +TypeGuard: _SpecialForm - def __or__(self, other: Any) -> _SpecialForm: ... - def __ror__(self, other: Any) -> _SpecialForm: ... - __supertype__: type | NewType - __name__: str +class NewType: + def __init__(self, name: str, tp: Any) -> None: ... # AnnotationForm + if sys.version_info >= (3, 11): + @staticmethod + def __call__(x: _T, /) -> _T: ... + else: + def __call__(self, x: _T) -> _T: ... -else: - def NewType(name: str, tp: Any) -> Any: ... + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... + __supertype__: type | NewType + __name__: str _F = TypeVar("_F", bound=Callable[..., Any]) -_P = _ParamSpec("_P") +_P = ParamSpec("_P") _T = TypeVar("_T") _FT = TypeVar("_FT", bound=Callable[..., Any] | type) @@ -410,12 +422,12 @@ _TC = TypeVar("_TC", bound=type[object]) def overload(func: _F) -> _F: ... def no_type_check(arg: _F) -> _F: ... -if sys.version_info >= (3, 13): +if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; removed in Python 3.15.") def no_type_check_decorator(decorator: Callable[_P, _T]) -> Callable[_P, _T]: ... -else: - def no_type_check_decorator(decorator: Callable[_P, _T]) -> Callable[_P, _T]: ... +if sys.version_info >= (3, 15): + def disjoint_base(cls: _TC) -> _TC: ... # This itself is only available during type checking def type_check_only(func_or_cls: _FT) -> _FT: ... @@ -439,6 +451,13 @@ ChainMap = _Alias() OrderedDict = _Alias() Annotated: _SpecialForm +if sys.version_info >= (3, 15): + @type_check_only + class _NoExtraItemsType: ... + + NoExtraItems: _NoExtraItemsType + + TypeForm: _SpecialForm # Predefined type variables. AnyStr = TypeVar("AnyStr", str, bytes) # noqa: Y001 @@ -448,12 +467,8 @@ class _Generic: if sys.version_info < (3, 12): __slots__ = () - if sys.version_info >= (3, 10): - @classmethod - def __class_getitem__(cls, args: TypeVar | ParamSpec | tuple[TypeVar | ParamSpec, ...]) -> _Final: ... - else: - @classmethod - def __class_getitem__(cls, args: TypeVar | tuple[TypeVar, ...]) -> _Final: ... + @classmethod + def __class_getitem__(cls, args: TypeVar | ParamSpec | tuple[TypeVar | ParamSpec, ...]) -> _Final: ... Generic: type[_Generic] @@ -464,6 +479,7 @@ class _ProtocolMeta(ABCMeta): # Abstract base classes. def runtime_checkable(cls: _TC) -> _TC: ... + @runtime_checkable class SupportsInt(Protocol, metaclass=ABCMeta): __slots__ = () @@ -503,6 +519,7 @@ class SupportsAbs(Protocol[_T_co]): @runtime_checkable class SupportsRound(Protocol[_T_co]): __slots__ = () + @overload @abstractmethod def __round__(self) -> int: ... @@ -548,6 +565,7 @@ class Generator(Iterator[_YieldT_co], Protocol[_YieldT_co, _SendT_contra, _Retur def __next__(self) -> _YieldT_co: ... @abstractmethod def send(self, value: _SendT_contra, /) -> _YieldT_co: ... + @overload @abstractmethod def throw( @@ -556,6 +574,7 @@ class Generator(Iterator[_YieldT_co], Protocol[_YieldT_co, _SendT_contra, _Retur @overload @abstractmethod def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = None, /) -> _YieldT_co: ... + if sys.version_info >= (3, 13): def close(self) -> _ReturnT_co | None: ... else: @@ -590,6 +609,7 @@ class Coroutine(Awaitable[_ReturnT_nd_co], Generic[_YieldT_co, _SendT_nd_contra, @abstractmethod def send(self, value: _SendT_nd_contra, /) -> _YieldT_co: ... + @overload @abstractmethod def throw( @@ -598,6 +618,7 @@ class Coroutine(Awaitable[_ReturnT_nd_co], Generic[_YieldT_co, _SendT_nd_contra, @overload @abstractmethod def throw(self, typ: BaseException, val: None = None, tb: TracebackType | None = None, /) -> _YieldT_co: ... + @abstractmethod def close(self) -> None: ... @@ -628,6 +649,7 @@ class AsyncGenerator(AsyncIterator[_YieldT_co], Protocol[_YieldT_co, _SendT_cont def __anext__(self) -> Coroutine[Any, Any, _YieldT_co]: ... @abstractmethod def asend(self, value: _SendT_contra, /) -> Coroutine[Any, Any, _YieldT_co]: ... + @overload @abstractmethod def athrow( @@ -638,6 +660,7 @@ class AsyncGenerator(AsyncIterator[_YieldT_co], Protocol[_YieldT_co, _SendT_cont def athrow( self, typ: BaseException, val: None = None, tb: TracebackType | None = None, / ) -> Coroutine[Any, Any, _YieldT_co]: ... + def aclose(self) -> Coroutine[Any, Any, None]: ... @runtime_checkable @@ -659,6 +682,7 @@ class Sequence(Reversible[_T_co], Collection[_T_co]): @overload @abstractmethod def __getitem__(self, index: slice[int | None], /) -> Sequence[_T_co]: ... + # Mixin methods def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... def count(self, value: Any, /) -> int: ... @@ -669,24 +693,28 @@ class Sequence(Reversible[_T_co], Collection[_T_co]): class MutableSequence(Sequence[_T]): @abstractmethod def insert(self, index: int, value: _T, /) -> None: ... + @overload @abstractmethod def __getitem__(self, index: int, /) -> _T: ... @overload @abstractmethod def __getitem__(self, index: slice[int | None], /) -> MutableSequence[_T]: ... + @overload @abstractmethod def __setitem__(self, index: int, value: _T, /) -> None: ... @overload @abstractmethod def __setitem__(self, index: slice[int | None], value: Iterable[_T], /) -> None: ... + @overload @abstractmethod def __delitem__(self, index: int, /) -> None: ... @overload @abstractmethod def __delitem__(self, index: slice[int | None], /) -> None: ... + # Mixin methods def append(self, value: _T, /) -> None: ... def clear(self) -> None: ... @@ -778,6 +806,7 @@ class Mapping(Collection[_KT], Generic[_KT, _VT_co]): # see discussion in https://github.com/python/typing/pull/273. @abstractmethod def __getitem__(self, key: _KT, /) -> _VT_co: ... + # Mixin methods @overload def get(self, key: _KT, /) -> _VT_co | None: ... @@ -785,6 +814,7 @@ class Mapping(Collection[_KT], Generic[_KT, _VT_co]): def get(self, key: _KT, default: _VT_co, /) -> _VT_co: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter @overload def get(self, key: _KT, default: _T, /) -> _VT_co | _T: ... + def items(self) -> ItemsView[_KT, _VT_co]: ... def keys(self) -> KeysView[_KT]: ... def values(self) -> ValuesView[_VT_co]: ... @@ -797,13 +827,16 @@ class MutableMapping(Mapping[_KT, _VT]): @abstractmethod def __delitem__(self, key: _KT, /) -> None: ... def clear(self) -> None: ... + @overload def pop(self, key: _KT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _VT, /) -> _VT: ... @overload def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def popitem(self) -> tuple[_KT, _VT]: ... + # This overload should be allowed only if the value type is compatible with None. # # Keep the following methods in line with MutableMapping.setdefault, modulo positional-only differences: @@ -814,6 +847,7 @@ class MutableMapping(Mapping[_KT, _VT]): def setdefault(self: MutableMapping[_KT, _T | None], key: _KT, default: None = None, /) -> _T | None: ... @overload def setdefault(self, key: _KT, default: _VT, /) -> _VT: ... + # 'update' used to take a Union, but using overloading is better. # The second overloaded type here is a bit too general, because # Mapping[tuple[_KT, _VT], W] is a subclass of Iterable[tuple[_KT, _VT]], @@ -891,18 +925,21 @@ class IO(Generic[AnyStr]): def truncate(self, size: int | None = None, /) -> int: ... @abstractmethod def writable(self) -> bool: ... + @abstractmethod @overload def write(self: IO[bytes], s: ReadableBuffer, /) -> int: ... @abstractmethod @overload def write(self, s: AnyStr, /) -> int: ... + @abstractmethod @overload def writelines(self: IO[bytes], lines: Iterable[ReadableBuffer], /) -> None: ... @abstractmethod @overload def writelines(self, lines: Iterable[AnyStr], /) -> None: ... + @abstractmethod def __next__(self) -> AnyStr: ... @abstractmethod @@ -971,16 +1008,15 @@ else: def get_args(tp: Any) -> tuple[Any, ...]: ... # AnnotationForm -if sys.version_info >= (3, 10): - @overload - def get_origin(tp: ParamSpecArgs | ParamSpecKwargs) -> ParamSpec: ... - @overload - def get_origin(tp: UnionType) -> type[UnionType]: ... - +@overload +def get_origin(tp: ParamSpecArgs | ParamSpecKwargs) -> ParamSpec: ... +@overload +def get_origin(tp: UnionType) -> type[UnionType]: ... @overload def get_origin(tp: GenericAlias) -> type: ... @overload def get_origin(tp: Any) -> Any | None: ... # AnnotationForm + @overload def cast(typ: type[_T], val: Any) -> _T: ... @overload @@ -1010,6 +1046,7 @@ if sys.version_info >= (3, 11): class NamedTuple(tuple[Any, ...]): _field_defaults: ClassVar[dict[str, Any]] _fields: ClassVar[tuple[str, ...]] + __match_args__: ClassVar[tuple[str, ...]] = ... # __orig_bases__ sometimes exists on <3.12, but not consistently # So we only add it to the stub on 3.12+. if sys.version_info >= (3, 12): @@ -1020,9 +1057,13 @@ class NamedTuple(tuple[Any, ...]): @overload @deprecated("Creating a typing.NamedTuple using keyword arguments is deprecated and support will be removed in Python 3.15") def __init__(self, typename: str, fields: None = None, /, **kwargs: Any) -> None: ... + + @final @classmethod def _make(cls, iterable: Iterable[Any]) -> typing_extensions.Self: ... + @final def _asdict(self) -> dict[str, Any]: ... + @final def _replace(self, **kwargs: Any) -> typing_extensions.Self: ... if sys.version_info >= (3, 13): def __replace__(self, **kwargs: Any) -> typing_extensions.Self: ... @@ -1042,6 +1083,10 @@ class _TypedDict(Mapping[str, object], metaclass=ABCMeta): if sys.version_info >= (3, 13): __readonly_keys__: ClassVar[frozenset[str]] __mutable_keys__: ClassVar[frozenset[str]] + if sys.version_info >= (3, 15): + # PEP 728 + __closed__: ClassVar[bool | None] + __extra_items__: ClassVar[Any] # AnnotationForm def copy(self) -> typing_extensions.Self: ... # Using Never so that only calls using mypy plugin hook that specialize the signature @@ -1054,14 +1099,17 @@ class _TypedDict(Mapping[str, object], metaclass=ABCMeta): def items(self) -> dict_items[str, object]: ... def keys(self) -> dict_keys[str, object]: ... def values(self) -> dict_values[str, object]: ... + @overload def __or__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... @overload def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload def __ror__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... @overload def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of __or__ and __ior__ def __ior__(self, value: typing_extensions.Self, /) -> typing_extensions.Self: ... # type: ignore[misc] @@ -1139,9 +1187,7 @@ else: def __or__(self, other: Any) -> _SpecialForm: ... def __ror__(self, other: Any) -> _SpecialForm: ... -if sys.version_info >= (3, 10): - def is_typeddict(tp: object) -> bool: ... - +def is_typeddict(tp: object) -> bool: ... def _type_repr(obj: object) -> str: ... if sys.version_info >= (3, 12): @@ -1155,6 +1201,7 @@ if sys.version_info >= (3, 12): ) def override(method: _F, /) -> _F: ... + @final class TypeAliasType: def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ... @@ -1166,6 +1213,9 @@ if sys.version_info >= (3, 12): def __parameters__(self) -> tuple[Any, ...]: ... # AnnotationForm @property def __name__(self) -> str: ... + if sys.version_info >= (3, 15): + @property + def __qualname__(self) -> str: ... # It's writable on types, but not on instances of TypeAliasType. @property def __module__(self) -> str | None: ... # type: ignore[override] @@ -1173,12 +1223,14 @@ if sys.version_info >= (3, 12): def __or__(self, right: Any, /) -> _SpecialForm: ... def __ror__(self, left: Any, /) -> _SpecialForm: ... if sys.version_info >= (3, 14): + def __iter__(self) -> Any: ... # Unpack[Self] @property def evaluate_value(self) -> EvaluateFunc: ... if sys.version_info >= (3, 13): def is_protocol(tp: type, /) -> bool: ... def get_protocol_members(tp: type, /) -> frozenset[str]: ... + @final @type_check_only class _NoDefaultType: ... diff --git a/mypy/typeshed/stdlib/typing_extensions.pyi b/mypy/typeshed/stdlib/typing_extensions.pyi index 406005cc4c561..80341175e6e14 100644 --- a/mypy/typeshed/stdlib/typing_extensions.pyi +++ b/mypy/typeshed/stdlib/typing_extensions.pyi @@ -29,8 +29,8 @@ from collections.abc import ( ) from contextlib import AbstractAsyncContextManager as AsyncContextManager, AbstractContextManager as ContextManager from re import Match as Match, Pattern as Pattern -from types import GenericAlias, ModuleType -from typing import ( # noqa: Y022,Y037,Y038,Y039,UP035,RUF100 +from types import GenericAlias, ModuleType, UnionType +from typing import ( # noqa: Y022,Y037,Y038,Y039,UP035 IO as IO, TYPE_CHECKING as TYPE_CHECKING, AbstractSet as AbstractSet, @@ -40,6 +40,7 @@ from typing import ( # noqa: Y022,Y037,Y038,Y039,UP035,RUF100 Callable as Callable, ChainMap as ChainMap, ClassVar as ClassVar, + Concatenate as Concatenate, Counter as Counter, DefaultDict as DefaultDict, Deque as Deque, @@ -50,25 +51,28 @@ from typing import ( # noqa: Y022,Y037,Y038,Y039,UP035,RUF100 List as List, NoReturn as NoReturn, Optional as Optional, + ParamSpecArgs as ParamSpecArgs, + ParamSpecKwargs as ParamSpecKwargs, Set as Set, Text as Text, TextIO as TextIO, Tuple as Tuple, Type as Type, - TypedDict as TypedDict, + TypeAlias as TypeAlias, + TypeGuard as TypeGuard, TypeVar as _TypeVar, Union as Union, _Alias, _SpecialForm, cast as cast, + is_typeddict as is_typeddict, no_type_check as no_type_check, - no_type_check_decorator as no_type_check_decorator, overload as overload, type_check_only, ) -if sys.version_info >= (3, 10): - from types import UnionType +if sys.version_info >= (3, 14): + from _typeshed import EvaluateFunc # Please keep order the same as at runtime. __all__ = [ @@ -141,6 +145,7 @@ __all__ = [ "override", "Protocol", "Sentinel", + "sentinel", "reveal_type", "runtime", "runtime_checkable", @@ -207,6 +212,9 @@ _TC = _TypeVar("_TC", bound=type[object]) _T_co = _TypeVar("_T_co", covariant=True) # Any type covariant containers. _T_contra = _TypeVar("_T_contra", contravariant=True) +if sys.version_info < (3, 15): + def no_type_check_decorator(decorator: _F) -> _F: ... + # Do not import (and re-export) Protocol or runtime_checkable from # typing module because type checkers need to be able to distinguish # typing.Protocol and typing_extensions.Protocol so they can properly @@ -227,6 +235,11 @@ Literal: _SpecialForm def IntVar(name: str) -> Any: ... # returns a new TypeVar +# Kept as a distinct symbol to `typing.TypedDict` so that type checkers can more easily +# distinguish between the two on Python 3.14, on which `typing_extensions.TypedDict` +# exposes `__closed__` and `__extra_items__` but `typing.TypedDict` does not +TypedDict: _SpecialForm + # Internal mypy fallback type for all typed dicts (does not exist at runtime) # N.B. Keep this mostly in sync with typing._TypedDict/mypy_extensions._TypedDict @type_check_only @@ -252,14 +265,17 @@ class _TypedDict(Mapping[str, object], metaclass=abc.ABCMeta): def keys(self) -> dict_keys[str, object]: ... def values(self) -> dict_values[str, object]: ... def __delitem__(self, k: Never) -> None: ... + @overload def __or__(self, value: Self, /) -> Self: ... @overload def __or__(self, value: dict[str, Any], /) -> dict[str, object]: ... + @overload def __ror__(self, value: Self, /) -> Self: ... @overload def __ror__(self, value: dict[str, Any], /) -> dict[str, object]: ... + # supposedly incompatible definitions of `__ior__` and `__or__`: # Since this module defines "Self" it is not recognized by Ruff as typing_extensions.Self def __ior__(self, value: Self, /) -> Self: ... # type: ignore[misc] @@ -275,10 +291,8 @@ else: def get_args(tp: AnnotationForm) -> tuple[AnnotationForm, ...]: ... -if sys.version_info >= (3, 10): - @overload - def get_origin(tp: UnionType) -> type[UnionType]: ... - +@overload +def get_origin(tp: UnionType) -> type[UnionType]: ... @overload def get_origin(tp: GenericAlias) -> type: ... @overload @@ -289,34 +303,6 @@ def get_origin(tp: AnnotationForm) -> AnnotationForm | None: ... Annotated: _SpecialForm _AnnotatedAlias: Any # undocumented -# New and changed things in 3.10 -if sys.version_info >= (3, 10): - from typing import ( - Concatenate as Concatenate, - ParamSpecArgs as ParamSpecArgs, - ParamSpecKwargs as ParamSpecKwargs, - TypeAlias as TypeAlias, - TypeGuard as TypeGuard, - is_typeddict as is_typeddict, - ) -else: - @final - class ParamSpecArgs: - @property - def __origin__(self) -> ParamSpec: ... - def __init__(self, origin: ParamSpec) -> None: ... - - @final - class ParamSpecKwargs: - @property - def __origin__(self) -> ParamSpec: ... - def __init__(self, origin: ParamSpec) -> None: ... - - Concatenate: _SpecialForm - TypeAlias: _SpecialForm - TypeGuard: _SpecialForm - def is_typeddict(tp: object) -> bool: ... - # New and changed things in 3.11 if sys.version_info >= (3, 11): from typing import ( @@ -363,10 +349,12 @@ else: _field_defaults: ClassVar[dict[str, Any]] _fields: ClassVar[tuple[str, ...]] __orig_bases__: ClassVar[tuple[Any, ...]] + @overload def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ... @overload def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ... + @classmethod def _make(cls, iterable: Iterable[Any]) -> Self: ... def _asdict(self) -> dict[str, Any]: ... @@ -375,11 +363,10 @@ else: class NewType: def __init__(self, name: str, tp: AnnotationForm) -> None: ... def __call__(self, obj: _T, /) -> _T: ... + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... __supertype__: type | NewType __name__: str - if sys.version_info >= (3, 10): - def __or__(self, other: Any) -> _SpecialForm: ... - def __ror__(self, other: Any) -> _SpecialForm: ... if sys.version_info >= (3, 12): from collections.abc import Buffer as Buffer @@ -446,6 +433,7 @@ else: @runtime_checkable class SupportsRound(Protocol[_T_co]): __slots__ = () + @overload @abc.abstractmethod def __round__(self) -> int: ... @@ -476,7 +464,6 @@ if sys.version_info >= (3, 13): ReadOnly as ReadOnly, TypeIs as TypeIs, TypeVar as TypeVar, - TypeVarTuple as TypeVarTuple, get_protocol_members as get_protocol_members, is_protocol as is_protocol, ) @@ -484,6 +471,7 @@ if sys.version_info >= (3, 13): else: def is_protocol(tp: type, /) -> bool: ... def get_protocol_members(tp: type, /) -> frozenset[str]: ... + @final @type_check_only class _NoDefaultType: ... @@ -527,9 +515,8 @@ else: ) -> None: ... def has_default(self) -> bool: ... def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... - if sys.version_info >= (3, 10): - def __or__(self, right: Any) -> _SpecialForm: ... - def __ror__(self, left: Any) -> _SpecialForm: ... + def __or__(self, right: Any) -> _SpecialForm: ... + def __ror__(self, left: Any) -> _SpecialForm: ... if sys.version_info >= (3, 11): def __typing_subst__(self, arg: Any) -> Any: ... @@ -556,29 +543,67 @@ else: covariant: bool = False, default: AnnotationForm = ..., ) -> None: ... + def __or__(self, right: Any) -> _SpecialForm: ... + def __ror__(self, left: Any) -> _SpecialForm: ... @property def args(self) -> ParamSpecArgs: ... @property def kwargs(self) -> ParamSpecKwargs: ... def has_default(self) -> bool: ... def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... - if sys.version_info >= (3, 10): - def __or__(self, right: Any) -> _SpecialForm: ... - def __ror__(self, left: Any) -> _SpecialForm: ... + ReadOnly: _SpecialForm + TypeIs: _SpecialForm + +if sys.version_info >= (3, 15): + from typing import TypeVarTuple as TypeVarTuple +else: @final class TypeVarTuple: @property def __name__(self) -> str: ... @property + def __bound__(self) -> AnnotationForm | None: ... + @property + def __covariant__(self) -> bool: ... + @property + def __contravariant__(self) -> bool: ... + @property + def __infer_variance__(self) -> bool: ... + @property def __default__(self) -> AnnotationForm: ... - def __init__(self, name: str, *, default: AnnotationForm = ...) -> None: ... + if sys.version_info >= (3, 11): + def __new__( + cls, + name: str, + *, + bound: AnnotationForm | None = None, + covariant: bool = False, + contravariant: bool = False, + infer_variance: bool = False, + default: AnnotationForm = ..., + ) -> Self: ... + else: + def __init__( + self, + name: str, + *, + bound: AnnotationForm | None = None, + covariant: bool = False, + contravariant: bool = False, + infer_variance: bool = False, + default: AnnotationForm = ..., + ) -> None: ... + def __iter__(self) -> Any: ... # Unpack[Self] def has_default(self) -> bool: ... - def __typing_prepare_subst__(self, alias: Any, args: Any) -> tuple[Any, ...]: ... + if sys.version_info >= (3, 11): + def __typing_subst__(self, arg: Never, /) -> Never: ... - ReadOnly: _SpecialForm - TypeIs: _SpecialForm + def __typing_prepare_subst__(self, alias: Any, args: Any, /) -> tuple[Any, ...]: ... + if sys.version_info >= (3, 14): + @property + def evaluate_default(self) -> EvaluateFunc | None: ... # TypeAliasType was added in Python 3.12, but had significant changes in 3.14. if sys.version_info >= (3, 14): @@ -605,9 +630,8 @@ else: # Returns typing._GenericAlias, which isn't stubbed. def __getitem__(self, parameters: Incomplete | tuple[Incomplete, ...]) -> AnnotationForm: ... def __init_subclass__(cls, *args: Unused, **kwargs: Unused) -> NoReturn: ... - if sys.version_info >= (3, 10): - def __or__(self, right: Any, /) -> _SpecialForm: ... - def __ror__(self, left: Any, /) -> _SpecialForm: ... + def __or__(self, right: Any, /) -> _SpecialForm: ... + def __ror__(self, left: Any, /) -> _SpecialForm: ... # PEP 727 class Doc: @@ -664,6 +688,7 @@ else: eval_str: bool = False, format: Format = Format.VALUE, # noqa: Y011 ) -> dict[str, AnnotationForm]: ... + @overload def evaluate_forward_ref( forward_ref: ForwardRef, @@ -697,14 +722,25 @@ else: format: Format | None = None, _recursive_guard: Container[str] = ..., ) -> AnnotationForm: ... + def type_repr(value: object) -> str: ... # PEP 661 -class Sentinel: - def __init__(self, name: str, repr: str | None = None) -> None: ... - if sys.version_info >= (3, 14): - def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - elif sys.version_info >= (3, 10): - def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions +if sys.version_info >= (3, 15): + from builtins import sentinel as sentinel +else: + class sentinel: + def __init__(self, name: str, /, *, repr: str | None = None) -> None: ... + __name__: str + __module__: str + if sys.version_info >= (3, 14): + # `other`` can be any type form legal for unions. + # `x | x` creates a `sentinel` instance if `x` is a sentinel, not a `UnionType` instance + def __or__(self, other: Any) -> UnionType | sentinel: ... + def __ror__(self, other: Any) -> UnionType | sentinel: ... + else: + # other can be any type form legal for unions + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... + +Sentinel = sentinel diff --git a/mypy/typeshed/stdlib/unicodedata.pyi b/mypy/typeshed/stdlib/unicodedata.pyi index 9fff042f0b964..82a73181eed3b 100644 --- a/mypy/typeshed/stdlib/unicodedata.pyi +++ b/mypy/typeshed/stdlib/unicodedata.pyi @@ -1,14 +1,11 @@ import sys from _typeshed import ReadOnlyBuffer -from typing import Any, Final, Literal, TypeVar, final, overload -from typing_extensions import TypeAlias +from collections.abc import Iterator +from typing import Final, Literal, TypeAlias, TypeVar, final, overload ucd_3_2_0: UCD unidata_version: Final[str] -if sys.version_info < (3, 10): - ucnhash_CAPI: Any - _T = TypeVar("_T") _NormalizationForm: TypeAlias = Literal["NFC", "NFD", "NFKC", "NFKD"] @@ -16,11 +13,14 @@ _NormalizationForm: TypeAlias = Literal["NFC", "NFD", "NFKC", "NFKD"] def bidirectional(chr: str, /) -> str: ... def category(chr: str, /) -> str: ... def combining(chr: str, /) -> int: ... + @overload def decimal(chr: str, /) -> int: ... @overload def decimal(chr: str, default: _T, /) -> int | _T: ... + def decomposition(chr: str, /) -> str: ... + @overload def digit(chr: str, /) -> int: ... @overload @@ -30,17 +30,31 @@ _EastAsianWidth: TypeAlias = Literal["F", "H", "W", "Na", "A", "N"] def east_asian_width(chr: str, /) -> _EastAsianWidth: ... def is_normalized(form: _NormalizationForm, unistr: str, /) -> bool: ... + +if sys.version_info >= (3, 15): + def block(chr: str, /) -> str: ... + def extended_pictographic(chr: str, /) -> bool: ... + def grapheme_cluster_break(chr: str, /) -> str: ... + def indic_conjunct_break(chr: str, /) -> str: ... + def isxidstart(chr: str, /) -> bool: ... + def isxidcontinue(chr: str, /) -> bool: ... + def iter_graphemes(unistr: str, start: int = 0, end: int = sys.maxsize, /) -> Iterator[str]: ... + def lookup(name: str | ReadOnlyBuffer, /) -> str: ... def mirrored(chr: str, /) -> int: ... + @overload def name(chr: str, /) -> str: ... @overload def name(chr: str, default: _T, /) -> str | _T: ... + def normalize(form: _NormalizationForm, unistr: str, /) -> str: ... + @overload def numeric(chr: str, /) -> float: ... @overload def numeric(chr: str, default: _T, /) -> float | _T: ... + @final class UCD: # The methods below are constructed from the same array in C @@ -49,24 +63,31 @@ class UCD: def bidirectional(self, chr: str, /) -> str: ... def category(self, chr: str, /) -> str: ... def combining(self, chr: str, /) -> int: ... + @overload def decimal(self, chr: str, /) -> int: ... @overload def decimal(self, chr: str, default: _T, /) -> int | _T: ... + def decomposition(self, chr: str, /) -> str: ... + @overload def digit(self, chr: str, /) -> int: ... @overload def digit(self, chr: str, default: _T, /) -> int | _T: ... + def east_asian_width(self, chr: str, /) -> _EastAsianWidth: ... def is_normalized(self, form: _NormalizationForm, unistr: str, /) -> bool: ... def lookup(self, name: str | ReadOnlyBuffer, /) -> str: ... def mirrored(self, chr: str, /) -> int: ... + @overload def name(self, chr: str, /) -> str: ... @overload def name(self, chr: str, default: _T, /) -> str | _T: ... + def normalize(self, form: _NormalizationForm, unistr: str, /) -> str: ... + @overload def numeric(self, chr: str, /) -> float: ... @overload diff --git a/mypy/typeshed/stdlib/unittest/_log.pyi b/mypy/typeshed/stdlib/unittest/_log.pyi index 011a970d8bbce..da6ce5a5ac7ae 100644 --- a/mypy/typeshed/stdlib/unittest/_log.pyi +++ b/mypy/typeshed/stdlib/unittest/_log.pyi @@ -15,11 +15,13 @@ class _AssertLogsContext(_BaseTestCaseContext, Generic[_L]): logger_name: str level: int msg: None - if sys.version_info >= (3, 10): - def __init__(self, test_case: TestCase, logger_name: str, level: int, no_logs: bool) -> None: ... - no_logs: bool + no_logs: bool + if sys.version_info >= (3, 15): + def __init__( + self, test_case: TestCase, logger_name: str, level: int, no_logs: bool, formatter: logging.Formatter | None = None + ) -> None: ... else: - def __init__(self, test_case: TestCase, logger_name: str, level: int) -> None: ... + def __init__(self, test_case: TestCase, logger_name: str, level: int, no_logs: bool) -> None: ... def __enter__(self) -> _L: ... def __exit__( diff --git a/mypy/typeshed/stdlib/unittest/async_case.pyi b/mypy/typeshed/stdlib/unittest/async_case.pyi index 0b3fb9122c7b9..77627a85ef197 100644 --- a/mypy/typeshed/stdlib/unittest/async_case.pyi +++ b/mypy/typeshed/stdlib/unittest/async_case.pyi @@ -1,8 +1,7 @@ import sys from asyncio.events import AbstractEventLoop from collections.abc import Awaitable, Callable -from typing import TypeVar -from typing_extensions import ParamSpec +from typing import ParamSpec, TypeVar from .case import TestCase diff --git a/mypy/typeshed/stdlib/unittest/case.pyi b/mypy/typeshed/stdlib/unittest/case.pyi index a602196e73c64..942154ea53aea 100644 --- a/mypy/typeshed/stdlib/unittest/case.pyi +++ b/mypy/typeshed/stdlib/unittest/case.pyi @@ -7,8 +7,21 @@ from collections.abc import Callable, Container, Iterable, Mapping, Sequence, Se from contextlib import AbstractContextManager from re import Pattern from types import GenericAlias, TracebackType -from typing import Any, AnyStr, Final, Generic, NoReturn, Protocol, SupportsAbs, SupportsRound, TypeVar, overload, type_check_only -from typing_extensions import Never, ParamSpec, Self +from typing import ( + Any, + AnyStr, + Final, + Generic, + NoReturn, + ParamSpec, + Protocol, + SupportsAbs, + SupportsRound, + TypeVar, + overload, + type_check_only, +) +from typing_extensions import Never, Self from unittest._log import _AssertLogsContext, _LoggingWatcher from warnings import WarningMessage @@ -54,7 +67,7 @@ def skipIf(condition: object, reason: str) -> Callable[[_FT], _FT]: ... def skipUnless(condition: object, reason: str) -> Callable[[_FT], _FT]: ... class SkipTest(Exception): - def __init__(self, reason: str) -> None: ... + def __init__(self, reason: str, /) -> None: ... @type_check_only class _SupportsAbsAndDunderGE(SupportsDunderGE[Any], SupportsAbs[Any], Protocol): ... @@ -96,22 +109,27 @@ class TestCase: def assertNotIn(self, member: Any, container: Iterable[Any] | Container[Any], msg: Any = None) -> None: ... def assertIsInstance(self, obj: object, cls: _ClassInfo, msg: Any = None) -> None: ... def assertNotIsInstance(self, obj: object, cls: _ClassInfo, msg: Any = None) -> None: ... + @overload def assertGreater(self, a: SupportsDunderGT[_T], b: _T, msg: Any = None) -> None: ... @overload def assertGreater(self, a: _T, b: SupportsDunderLT[_T], msg: Any = None) -> None: ... + @overload def assertGreaterEqual(self, a: SupportsDunderGE[_T], b: _T, msg: Any = None) -> None: ... @overload def assertGreaterEqual(self, a: _T, b: SupportsDunderLE[_T], msg: Any = None) -> None: ... + @overload def assertLess(self, a: SupportsDunderLT[_T], b: _T, msg: Any = None) -> None: ... @overload def assertLess(self, a: _T, b: SupportsDunderGT[_T], msg: Any = None) -> None: ... + @overload def assertLessEqual(self, a: SupportsDunderLE[_T], b: _T, msg: Any = None) -> None: ... @overload def assertLessEqual(self, a: _T, b: SupportsDunderGE[_T], msg: Any = None) -> None: ... + # `assertRaises`, `assertRaisesRegex`, and `assertRaisesRegexp` # are not using `ParamSpec` intentionally, # because they might be used with explicitly wrong arg types to raise some error in tests. @@ -127,6 +145,7 @@ class TestCase: def assertRaises( self, expected_exception: type[_E] | tuple[type[_E], ...], *, msg: Any = ... ) -> _AssertRaisesContext[_E]: ... + @overload def assertRaisesRegex( self, @@ -140,6 +159,7 @@ class TestCase: def assertRaisesRegex( self, expected_exception: type[_E] | tuple[type[_E], ...], expected_regex: str | Pattern[str], *, msg: Any = ... ) -> _AssertRaisesContext[_E]: ... + @overload def assertWarns( self, @@ -152,6 +172,7 @@ class TestCase: def assertWarns( self, expected_warning: type[Warning] | tuple[type[Warning], ...], *, msg: Any = ... ) -> _AssertWarnsContext: ... + @overload def assertWarnsRegex( self, @@ -165,13 +186,22 @@ class TestCase: def assertWarnsRegex( self, expected_warning: type[Warning] | tuple[type[Warning], ...], expected_regex: str | Pattern[str], *, msg: Any = ... ) -> _AssertWarnsContext: ... - def assertLogs( - self, logger: str | logging.Logger | None = None, level: int | str | None = None - ) -> _AssertLogsContext[_LoggingWatcher]: ... - if sys.version_info >= (3, 10): - def assertNoLogs( + + if sys.version_info >= (3, 15): + def assertLogs( + self, + logger: str | logging.Logger | None = None, + level: int | str | None = None, + formatter: logging.Formatter | None = None, + ) -> _AssertLogsContext[_LoggingWatcher]: ... + else: + def assertLogs( self, logger: str | logging.Logger | None = None, level: int | str | None = None - ) -> _AssertLogsContext[None]: ... + ) -> _AssertLogsContext[_LoggingWatcher]: ... + + def assertNoLogs( + self, logger: str | logging.Logger | None = None, level: int | str | None = None + ) -> _AssertLogsContext[None]: ... @overload def assertAlmostEqual(self, first: _S, second: _S, places: None, msg: Any, delta: _SupportsAbsAndDunderGE) -> None: ... @@ -197,6 +227,7 @@ class TestCase: msg: Any = None, delta: None = None, ) -> None: ... + @overload def assertNotAlmostEqual(self, first: _S, second: _S, places: None, msg: Any, delta: _SupportsAbsAndDunderGE) -> None: ... @overload @@ -221,6 +252,7 @@ class TestCase: msg: Any = None, delta: None = None, ) -> None: ... + def assertRegex(self, text: AnyStr, expected_regex: AnyStr | Pattern[AnyStr], msg: Any = None) -> None: ... def assertNotRegex(self, text: AnyStr, unexpected_regex: AnyStr | Pattern[AnyStr], msg: Any = None) -> None: ... def assertCountEqual(self, first: Iterable[Any], second: Iterable[Any], msg: Any = None) -> None: ... @@ -277,9 +309,8 @@ class TestCase: self, subset: Mapping[Any, Any], dictionary: Mapping[Any, Any], msg: object = None ) -> None: ... - if sys.version_info >= (3, 10): - # Runtime has *args, **kwargs, but will error if any are supplied - def __init_subclass__(cls, *args: Never, **kwargs: Never) -> None: ... + # Runtime has *args, **kwargs, but will error if any are supplied + def __init_subclass__(cls, *args: Never, **kwargs: Never) -> None: ... if sys.version_info >= (3, 14): def assertIsSubclass(self, cls: type, superclass: type | tuple[type, ...], msg: Any = None) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/loader.pyi b/mypy/typeshed/stdlib/unittest/loader.pyi index 81de40c898496..0d92b78f3461d 100644 --- a/mypy/typeshed/stdlib/unittest/loader.pyi +++ b/mypy/typeshed/stdlib/unittest/loader.pyi @@ -4,8 +4,8 @@ import unittest.suite from collections.abc import Callable, Sequence from re import Pattern from types import ModuleType -from typing import Any, Final -from typing_extensions import TypeAlias, deprecated +from typing import Any, Final, TypeAlias +from typing_extensions import deprecated _SortComparisonMethod: TypeAlias = Callable[[str, str], int] _SuiteClass: TypeAlias = Callable[[list[unittest.case.TestCase]], unittest.suite.TestSuite] @@ -35,38 +35,21 @@ class TestLoader: defaultTestLoader: TestLoader if sys.version_info < (3, 13): - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") - def getTestCaseNames( - testCaseClass: type[unittest.case.TestCase], - prefix: str, - sortUsing: _SortComparisonMethod = ..., - testNamePatterns: list[str] | None = None, - ) -> Sequence[str]: ... - @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") - def makeSuite( - testCaseClass: type[unittest.case.TestCase], - prefix: str = "test", - sortUsing: _SortComparisonMethod = ..., - suiteClass: _SuiteClass = ..., - ) -> unittest.suite.TestSuite: ... - @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") - def findTestCases( - module: ModuleType, prefix: str = "test", sortUsing: _SortComparisonMethod = ..., suiteClass: _SuiteClass = ... - ) -> unittest.suite.TestSuite: ... - else: - def getTestCaseNames( - testCaseClass: type[unittest.case.TestCase], - prefix: str, - sortUsing: _SortComparisonMethod = ..., - testNamePatterns: list[str] | None = None, - ) -> Sequence[str]: ... - def makeSuite( - testCaseClass: type[unittest.case.TestCase], - prefix: str = "test", - sortUsing: _SortComparisonMethod = ..., - suiteClass: _SuiteClass = ..., - ) -> unittest.suite.TestSuite: ... - def findTestCases( - module: ModuleType, prefix: str = "test", sortUsing: _SortComparisonMethod = ..., suiteClass: _SuiteClass = ... - ) -> unittest.suite.TestSuite: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") + def getTestCaseNames( + testCaseClass: type[unittest.case.TestCase], + prefix: str, + sortUsing: _SortComparisonMethod = ..., + testNamePatterns: list[str] | None = None, + ) -> Sequence[str]: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") + def makeSuite( + testCaseClass: type[unittest.case.TestCase], + prefix: str = "test", + sortUsing: _SortComparisonMethod = ..., + suiteClass: _SuiteClass = ..., + ) -> unittest.suite.TestSuite: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") + def findTestCases( + module: ModuleType, prefix: str = "test", sortUsing: _SortComparisonMethod = ..., suiteClass: _SuiteClass = ... + ) -> unittest.suite.TestSuite: ... diff --git a/mypy/typeshed/stdlib/unittest/main.pyi b/mypy/typeshed/stdlib/unittest/main.pyi index e1aeac32c5910..f48347c16ec39 100644 --- a/mypy/typeshed/stdlib/unittest/main.pyi +++ b/mypy/typeshed/stdlib/unittest/main.pyi @@ -64,11 +64,8 @@ class TestProgram: ) -> None: ... if sys.version_info < (3, 13): - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") - def usageExit(self, msg: Any = None) -> None: ... - else: - def usageExit(self, msg: Any = None) -> None: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") + def usageExit(self, msg: Any = None) -> None: ... def parseArgs(self, argv: list[str]) -> None: ... def createTests(self, from_discovery: bool = False, Loader: unittest.loader.TestLoader | None = None) -> None: ... diff --git a/mypy/typeshed/stdlib/unittest/mock.pyi b/mypy/typeshed/stdlib/unittest/mock.pyi index ef51d721297a1..1b6ab756d2533 100644 --- a/mypy/typeshed/stdlib/unittest/mock.pyi +++ b/mypy/typeshed/stdlib/unittest/mock.pyi @@ -3,8 +3,8 @@ from _typeshed import MaybeNone from collections.abc import Awaitable, Callable, Coroutine, Iterable, Mapping, Sequence from contextlib import _GeneratorContextManager from types import TracebackType -from typing import Any, ClassVar, Final, Generic, Literal, TypeVar, overload, type_check_only -from typing_extensions import ParamSpec, Self, TypeAlias, disjoint_base +from typing import Any, ClassVar, Final, Generic, Literal, ParamSpec, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self, disjoint_base _T = TypeVar("_T") _TT = TypeVar("_TT", bound=type[Any]) @@ -244,42 +244,29 @@ class _patch(Generic[_T]): additional_patchers: Any # If new==DEFAULT, self is _patch[Any]. Ideally we'd be able to add an overload for it so that self is _patch[MagicMock], # but that's impossible with the current type system. - if sys.version_info >= (3, 10): - def __init__( - self: _patch[_T], # pyright: ignore[reportInvalidTypeVarUse] #11780 - getter: Callable[[], Any], - attribute: str, - new: _T, - spec: Any | None, - create: bool, - spec_set: Any | None, - autospec: Any | None, - new_callable: Any | None, - kwargs: Mapping[str, Any], - *, - unsafe: bool = False, - ) -> None: ... - else: - def __init__( - self: _patch[_T], # pyright: ignore[reportInvalidTypeVarUse] #11780 - getter: Callable[[], Any], - attribute: str, - new: _T, - spec: Any | None, - create: bool, - spec_set: Any | None, - autospec: Any | None, - new_callable: Any | None, - kwargs: Mapping[str, Any], - ) -> None: ... - + def __init__( + self: _patch[_T], # pyright: ignore[reportInvalidTypeVarUse] #11780 + getter: Callable[[], Any], + attribute: str, + new: _T, + spec: Any | None, + create: bool, + spec_set: Any | None, + autospec: Any | None, + new_callable: Any | None, + kwargs: Mapping[str, Any], + *, + unsafe: bool = False, + ) -> None: ... def copy(self) -> _patch[_T]: ... + @overload def __call__(self, func: _TT) -> _TT: ... # If new==DEFAULT, this should add a MagicMock parameter to the function # arguments. See the _patch_default_new class below for this functionality. @overload def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + def decoration_helper( self, patched: _patch[Any], args: Sequence[Any], keywargs: Any ) -> _GeneratorContextManager[tuple[Sequence[Any], Any]]: ... @@ -315,13 +302,11 @@ class _patch_dict: clear: Any def __init__(self, in_dict: Any, values: Any = (), clear: Any = False, **kwargs: Any) -> None: ... def __call__(self, f: Any) -> Any: ... - if sys.version_info >= (3, 10): - def decorate_callable(self, f: _F) -> _F: ... - def decorate_async_callable(self, f: _AF) -> _AF: ... - - def decorate_class(self, klass: Any) -> Any: ... def __enter__(self) -> Any: ... def __exit__(self, *args: object) -> Any: ... + def decorate_callable(self, f: _F) -> _F: ... + def decorate_async_callable(self, f: _AF) -> _AF: ... + def decorate_class(self, klass: Any) -> Any: ... start: Any stop: Any @@ -331,6 +316,7 @@ class _patch_dict: class _patcher: TEST_PREFIX: str dict: type[_patch_dict] + # This overload also covers the case, where new==DEFAULT. In this case, the return type is _patch[Any]. # Ideally we'd be able to add an overload for it so that the return type is _patch[MagicMock], # but that's impossible with the current type system. @@ -377,6 +363,7 @@ class _patcher: # kwargs are passed to the MagicMock/AsyncMock constructor **kwargs: Any, ) -> _patch_pass_arg[MagicMock | AsyncMock]: ... + # This overload also covers the case, where new==DEFAULT. In this case, the return type is _patch[Any]. # Ideally we'd be able to add an overload for it so that the return type is _patch[MagicMock], # but that's impossible with the current type system. @@ -426,6 +413,7 @@ class _patcher: # kwargs are passed to the MagicMock/AsyncMock constructor **kwargs: Any, ) -> _patch_pass_arg[MagicMock | AsyncMock]: ... + @overload @staticmethod def multiple( @@ -467,6 +455,7 @@ class _patcher: # The kwargs are the mock objects or DEFAULT **kwargs: Any, ) -> _patch[Any]: ... + @staticmethod def stopall() -> None: ... @@ -517,27 +506,16 @@ class _ANY(Any): ANY: _ANY -if sys.version_info >= (3, 10): - def create_autospec( - spec: Any, - spec_set: Any = False, - instance: Any = False, - _parent: Any | None = None, - _name: Any | None = None, - *, - unsafe: bool = False, - **kwargs: Any, - ) -> Any: ... - -else: - def create_autospec( - spec: Any, - spec_set: Any = False, - instance: Any = False, - _parent: Any | None = None, - _name: Any | None = None, - **kwargs: Any, - ) -> Any: ... +def create_autospec( + spec: Any, + spec_set: Any = False, + instance: Any = False, + _parent: Any | None = None, + _name: Any | None = None, + *, + unsafe: bool = False, + **kwargs: Any, +) -> Any: ... class _SpecState: spec: Any diff --git a/mypy/typeshed/stdlib/unittest/result.pyi b/mypy/typeshed/stdlib/unittest/result.pyi index 0761baaa2830b..081f6e1328e47 100644 --- a/mypy/typeshed/stdlib/unittest/result.pyi +++ b/mypy/typeshed/stdlib/unittest/result.pyi @@ -2,8 +2,7 @@ import sys import unittest.case from _typeshed import OptExcInfo from collections.abc import Callable -from typing import Any, Final, TextIO, TypeVar -from typing_extensions import TypeAlias +from typing import Any, Final, TextIO, TypeAlias, TypeVar _F = TypeVar("_F", bound=Callable[..., Any]) _DurationsType: TypeAlias = list[tuple[str, float]] diff --git a/mypy/typeshed/stdlib/unittest/runner.pyi b/mypy/typeshed/stdlib/unittest/runner.pyi index f76771f55e131..3a2e08749e9b6 100644 --- a/mypy/typeshed/stdlib/unittest/runner.pyi +++ b/mypy/typeshed/stdlib/unittest/runner.pyi @@ -4,8 +4,8 @@ import unittest.result import unittest.suite from _typeshed import SupportsFlush, SupportsWrite from collections.abc import Callable, Iterable -from typing import Any, Generic, Protocol, TypeVar, type_check_only -from typing_extensions import Never, TypeAlias +from typing import Any, Generic, Protocol, TypeAlias, TypeVar, type_check_only +from typing_extensions import Never from warnings import _ActionKind _ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult[Any]] diff --git a/mypy/typeshed/stdlib/unittest/signals.pyi b/mypy/typeshed/stdlib/unittest/signals.pyi index a60133ada9d95..928ab68ae65d1 100644 --- a/mypy/typeshed/stdlib/unittest/signals.pyi +++ b/mypy/typeshed/stdlib/unittest/signals.pyi @@ -1,7 +1,6 @@ import unittest.result from collections.abc import Callable -from typing import TypeVar, overload -from typing_extensions import ParamSpec +from typing import ParamSpec, TypeVar, overload _P = ParamSpec("_P") _T = TypeVar("_T") @@ -9,6 +8,7 @@ _T = TypeVar("_T") def installHandler() -> None: ... def registerResult(result: unittest.result.TestResult) -> None: ... def removeResult(result: unittest.result.TestResult) -> bool: ... + @overload def removeHandler(method: None = None) -> None: ... @overload diff --git a/mypy/typeshed/stdlib/unittest/suite.pyi b/mypy/typeshed/stdlib/unittest/suite.pyi index 443396164b6fe..b7cf75c682712 100644 --- a/mypy/typeshed/stdlib/unittest/suite.pyi +++ b/mypy/typeshed/stdlib/unittest/suite.pyi @@ -1,8 +1,7 @@ import unittest.case import unittest.result from collections.abc import Iterable, Iterator -from typing import ClassVar -from typing_extensions import TypeAlias +from typing import ClassVar, TypeAlias _TestType: TypeAlias = unittest.case.TestCase | TestSuite diff --git a/mypy/typeshed/stdlib/unittest/util.pyi b/mypy/typeshed/stdlib/unittest/util.pyi index 763c1478f5e6d..11a6f903932fb 100644 --- a/mypy/typeshed/stdlib/unittest/util.pyi +++ b/mypy/typeshed/stdlib/unittest/util.pyi @@ -1,6 +1,5 @@ from collections.abc import MutableSequence, Sequence -from typing import Any, Final, Literal, Protocol, TypeVar, type_check_only -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, Protocol, TypeAlias, TypeVar, type_check_only @type_check_only class _SupportsDunderLT(Protocol): diff --git a/mypy/typeshed/stdlib/urllib/error.pyi b/mypy/typeshed/stdlib/urllib/error.pyi index 2173d7e6efaa5..6255f1ae7db0d 100644 --- a/mypy/typeshed/stdlib/urllib/error.pyi +++ b/mypy/typeshed/stdlib/urllib/error.pyi @@ -15,6 +15,7 @@ class HTTPError(URLError, addinfourl): def headers(self) -> Message: ... @headers.setter def headers(self, headers: Message) -> None: ... + @property def reason(self) -> str: ... # type: ignore[override] code: int diff --git a/mypy/typeshed/stdlib/urllib/parse.pyi b/mypy/typeshed/stdlib/urllib/parse.pyi index 364892ecdf698..b83a0f4e8678c 100644 --- a/mypy/typeshed/stdlib/urllib/parse.pyi +++ b/mypy/typeshed/stdlib/urllib/parse.pyi @@ -1,8 +1,8 @@ import sys from collections.abc import Iterable, Mapping, Sequence from types import GenericAlias -from typing import Any, AnyStr, Final, Generic, Literal, NamedTuple, Protocol, overload, type_check_only -from typing_extensions import TypeAlias +from typing import Any, AnyStr, Final, Generic, Literal, NamedTuple, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import TypeVar __all__ = [ "urlparse", @@ -38,6 +38,11 @@ scheme_chars: Final[str] if sys.version_info < (3, 11): MAX_CACHE_SIZE: Final[int] +_ResultStrT = TypeVar("_ResultStrT", str, bytes) +_ResultComponentT = TypeVar("_ResultComponentT", str, bytes, str | None, bytes | None) +_StrComponentT = TypeVar("_StrComponentT", str, str | None, default=str) +_BytesComponentT = TypeVar("_BytesComponentT", bytes, bytes | None, default=bytes) + class _ResultMixinStr: __slots__ = () def encode(self, encoding: str = "ascii", errors: str = "strict") -> _ResultMixinBytes: ... @@ -64,44 +69,88 @@ class _NetlocResultMixinStr(_NetlocResultMixinBase[str], _ResultMixinStr): class _NetlocResultMixinBytes(_NetlocResultMixinBase[bytes], _ResultMixinBytes): __slots__ = () -class _DefragResultBase(NamedTuple, Generic[AnyStr]): - url: AnyStr - fragment: AnyStr +# Need to duplicate the whole class because mypy rejects version-specific +# branches in namedtuple bodies. +if sys.version_info >= (3, 15): + class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + url: _ResultStrT + fragment: _ResultComponentT + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] + +else: + class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + url: _ResultStrT + fragment: _ResultComponentT + +if sys.version_info >= (3, 15): + class _SplitResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + query: _ResultComponentT + fragment: _ResultComponentT + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] + +else: + class _SplitResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + query: _ResultComponentT + fragment: _ResultComponentT + +if sys.version_info >= (3, 15): + class _ParseResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + params: _ResultComponentT + query: _ResultComponentT + fragment: _ResultComponentT + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] -class _SplitResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - query: AnyStr - fragment: AnyStr +else: + class _ParseResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + params: _ResultComponentT + query: _ResultComponentT + fragment: _ResultComponentT -class _ParseResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - params: AnyStr - query: AnyStr - fragment: AnyStr +if sys.version_info >= (3, 15): + # Structured result objects for string data + class DefragResult(_DefragResultBase[str, _StrComponentT], _ResultMixinStr, Generic[_StrComponentT]): ... + class SplitResult(_SplitResultBase[str, _StrComponentT], _NetlocResultMixinStr, Generic[_StrComponentT]): ... + class ParseResult(_ParseResultBase[str, _StrComponentT], _NetlocResultMixinStr, Generic[_StrComponentT]): ... + # Structured result objects for bytes data + class DefragResultBytes(_DefragResultBase[bytes, _BytesComponentT], _ResultMixinBytes, Generic[_BytesComponentT]): ... + class SplitResultBytes(_SplitResultBase[bytes, _BytesComponentT], _NetlocResultMixinBytes, Generic[_BytesComponentT]): ... + class ParseResultBytes(_ParseResultBase[bytes, _BytesComponentT], _NetlocResultMixinBytes, Generic[_BytesComponentT]): ... -# Structured result objects for string data -class DefragResult(_DefragResultBase[str], _ResultMixinStr): - def geturl(self) -> str: ... +else: + # Structured result objects for string data + class DefragResult(_DefragResultBase[str, str], _ResultMixinStr): + def geturl(self) -> str: ... -class SplitResult(_SplitResultBase[str], _NetlocResultMixinStr): - def geturl(self) -> str: ... + class SplitResult(_SplitResultBase[str, str], _NetlocResultMixinStr): + def geturl(self) -> str: ... -class ParseResult(_ParseResultBase[str], _NetlocResultMixinStr): - def geturl(self) -> str: ... + class ParseResult(_ParseResultBase[str, str], _NetlocResultMixinStr): + def geturl(self) -> str: ... -# Structured result objects for bytes data -class DefragResultBytes(_DefragResultBase[bytes], _ResultMixinBytes): - def geturl(self) -> bytes: ... + # Structured result objects for bytes data + class DefragResultBytes(_DefragResultBase[bytes, bytes], _ResultMixinBytes): + def geturl(self) -> bytes: ... -class SplitResultBytes(_SplitResultBase[bytes], _NetlocResultMixinBytes): - def geturl(self) -> bytes: ... + class SplitResultBytes(_SplitResultBase[bytes, bytes], _NetlocResultMixinBytes): + def geturl(self) -> bytes: ... -class ParseResultBytes(_ParseResultBase[bytes], _NetlocResultMixinBytes): - def geturl(self) -> bytes: ... + class ParseResultBytes(_ParseResultBase[bytes, bytes], _NetlocResultMixinBytes): + def geturl(self) -> bytes: ... def parse_qs( qs: AnyStr | None, @@ -121,22 +170,40 @@ def parse_qsl( max_num_fields: int | None = None, separator: str = "&", ) -> list[tuple[AnyStr, AnyStr]]: ... + @overload def quote(string: str, safe: str | Iterable[int] = "/", encoding: str | None = None, errors: str | None = None) -> str: ... @overload def quote(string: bytes | bytearray, safe: str | Iterable[int] = "/") -> str: ... + def quote_from_bytes(bs: bytes | bytearray, safe: str | Iterable[int] = "/") -> str: ... + @overload def quote_plus(string: str, safe: str | Iterable[int] = "", encoding: str | None = None, errors: str | None = None) -> str: ... @overload def quote_plus(string: bytes | bytearray, safe: str | Iterable[int] = "") -> str: ... + def unquote(string: str | bytes, encoding: str = "utf-8", errors: str = "replace") -> str: ... def unquote_to_bytes(string: str | bytes | bytearray) -> bytes: ... def unquote_plus(string: str, encoding: str = "utf-8", errors: str = "replace") -> str: ... + @overload def urldefrag(url: str) -> DefragResult: ... @overload def urldefrag(url: bytes | bytearray | None) -> DefragResultBytes: ... +if sys.version_info >= (3, 15): + @overload + def urldefrag(url: str, *, missing_as_none: Literal[True]) -> DefragResult[str | None]: ... + @overload + def urldefrag(url: str, *, missing_as_none: Literal[False] = False) -> DefragResult[str]: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[True]) -> DefragResultBytes[bytes | None]: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[False] = False) -> DefragResultBytes[bytes]: ... + @overload + def urldefrag(url: str, *, missing_as_none: bool) -> DefragResult[str | None]: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: bool) -> DefragResultBytes[bytes | None]: ... # The values are passed through `str()` (unless they are bytes), so anything is valid. _QueryType: TypeAlias = ( @@ -166,12 +233,51 @@ def urlencode( quote_via: _QuoteVia = ..., ) -> str: ... def urljoin(base: AnyStr, url: AnyStr | None, allow_fragments: bool = True) -> AnyStr: ... + @overload def urlparse(url: str, scheme: str = "", allow_fragments: bool = True) -> ParseResult: ... @overload def urlparse( url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> ParseResultBytes: ... +if sys.version_info >= (3, 15): + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] + ) -> ParseResult[str | None]: ... + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False + ) -> ParseResult[str]: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[True], + ) -> ParseResultBytes[bytes | None]: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[False] = False, + ) -> ParseResultBytes[bytes]: ... + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> ParseResult[str | None]: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: bool, + ) -> ParseResultBytes[bytes | None]: ... + @overload def urlsplit(url: str, scheme: str = "", allow_fragments: bool = True) -> SplitResult: ... @@ -180,22 +286,69 @@ if sys.version_info >= (3, 11): def urlsplit( url: bytes | None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True ) -> SplitResultBytes: ... - else: @overload def urlsplit( url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> SplitResultBytes: ... +if sys.version_info >= (3, 15): + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] + ) -> SplitResult[str | None]: ... + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False + ) -> SplitResult[str]: ... + @overload + def urlsplit( + url: bytes | None, + scheme: bytes | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[True], + ) -> SplitResultBytes[bytes | None]: ... + @overload + def urlsplit( + url: bytes | None, + scheme: bytes | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[False] = False, + ) -> SplitResultBytes[bytes]: ... + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> SplitResult[str | None]: ... + @overload + def urlsplit( + url: bytes | None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> SplitResultBytes[bytes | None]: ... -# Requires an iterable of length 6 -@overload -def urlunparse(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] -@overload -def urlunparse(components: Iterable[AnyStr | None]) -> AnyStr: ... +if sys.version_info >= (3, 15): + # Requires an iterable of length 6 + @overload + def urlunparse(components: Iterable[None], *, keep_empty: bool = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunparse(components: Iterable[AnyStr | None], *, keep_empty: bool = ...) -> AnyStr: ... +else: + # Requires an iterable of length 6 + @overload + def urlunparse(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunparse(components: Iterable[AnyStr | None]) -> AnyStr: ... + +if sys.version_info >= (3, 15): + # Requires an iterable of length 5 + @overload + def urlunsplit(components: Iterable[None], *, keep_empty: bool = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunsplit(components: Iterable[AnyStr | None], *, keep_empty: bool = ...) -> AnyStr: ... +else: + # Requires an iterable of length 5 + @overload + def urlunsplit(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunsplit(components: Iterable[AnyStr | None]) -> AnyStr: ... -# Requires an iterable of length 5 -@overload -def urlunsplit(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] -@overload -def urlunsplit(components: Iterable[AnyStr | None]) -> AnyStr: ... def unwrap(url: str) -> str: ... diff --git a/mypy/typeshed/stdlib/urllib/request.pyi b/mypy/typeshed/stdlib/urllib/request.pyi index d6bb2f9647824..0ec26052b23c6 100644 --- a/mypy/typeshed/stdlib/urllib/request.pyi +++ b/mypy/typeshed/stdlib/urllib/request.pyi @@ -6,8 +6,8 @@ from email.message import Message from http.client import HTTPConnection, HTTPMessage, HTTPResponse from http.cookiejar import CookieJar from re import Pattern -from typing import IO, Any, ClassVar, Literal, NoReturn, Protocol, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias, deprecated +from typing import IO, Any, ClassVar, Literal, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import deprecated from urllib.error import HTTPError as HTTPError from urllib.response import addclosehook, addinfourl @@ -124,6 +124,7 @@ class Request: def full_url(self, value: str) -> None: ... @full_url.deleter def full_url(self) -> None: ... + type: str host: str origin_req_host: str @@ -150,10 +151,12 @@ class Request: def remove_header(self, header_name: str) -> None: ... def get_full_url(self) -> str: ... def set_proxy(self, host: str, type: str) -> None: ... + @overload def get_header(self, header_name: str) -> str | None: ... @overload def get_header(self, header_name: str, default: _T) -> str | _T: ... + def header_items(self) -> list[tuple[str, str]]: ... def has_proxy(self) -> bool: ... diff --git a/mypy/typeshed/stdlib/uu.pyi b/mypy/typeshed/stdlib/uu.pyi index 324053e04337c..62bf6cb05f59d 100644 --- a/mypy/typeshed/stdlib/uu.pyi +++ b/mypy/typeshed/stdlib/uu.pyi @@ -1,5 +1,4 @@ -from typing import BinaryIO -from typing_extensions import TypeAlias +from typing import BinaryIO, TypeAlias __all__ = ["Error", "encode", "decode"] diff --git a/mypy/typeshed/stdlib/uuid.pyi b/mypy/typeshed/stdlib/uuid.pyi index 055f4def311cd..06285e03aea7e 100644 --- a/mypy/typeshed/stdlib/uuid.pyi +++ b/mypy/typeshed/stdlib/uuid.pyi @@ -2,8 +2,8 @@ import builtins import sys from _typeshed import Unused from enum import Enum -from typing import Final, NoReturn -from typing_extensions import LiteralString, TypeAlias +from typing import Final, NoReturn, TypeAlias +from typing_extensions import LiteralString _FieldsType: TypeAlias = tuple[int, int, int, int, int, int] diff --git a/mypy/typeshed/stdlib/warnings.pyi b/mypy/typeshed/stdlib/warnings.pyi index 49c98cb07540e..e17b6e3a25b32 100644 --- a/mypy/typeshed/stdlib/warnings.pyi +++ b/mypy/typeshed/stdlib/warnings.pyi @@ -3,8 +3,8 @@ import sys from _warnings import warn as warn, warn_explicit as warn_explicit from collections.abc import Sequence from types import ModuleType, TracebackType -from typing import Any, Generic, Literal, TextIO, overload -from typing_extensions import LiteralString, TypeAlias, TypeVar +from typing import Any, Generic, Literal, TextIO, TypeAlias, overload +from typing_extensions import LiteralString, TypeVar __all__ = [ "warn", @@ -27,7 +27,9 @@ if sys.version_info >= (3, 14): _ActionKind: TypeAlias = Literal["default", "error", "ignore", "always", "module", "once"] else: _ActionKind: TypeAlias = Literal["default", "error", "ignore", "always", "all", "module", "once"] -filters: Sequence[tuple[str, re.Pattern[str] | None, type[Warning], re.Pattern[str] | None, int]] # undocumented, do not mutate +filters: Sequence[ + tuple[str, re.Pattern[str] | None, type[Warning] | tuple[type[Warning], ...], re.Pattern[str] | None, int] +] # undocumented, do not mutate def showwarning( message: Warning | str, @@ -43,7 +45,9 @@ def formatwarning( def filterwarnings( action: _ActionKind, message: str = "", category: type[Warning] = ..., module: str = "", lineno: int = 0, append: bool = False ) -> None: ... -def simplefilter(action: _ActionKind, category: type[Warning] = ..., lineno: int = 0, append: bool = False) -> None: ... +def simplefilter( + action: _ActionKind, category: type[Warning] | tuple[type[Warning], ...] = ..., lineno: int = 0, append: bool = False +) -> None: ... def resetwarnings() -> None: ... class _OptionError(Exception): ... @@ -56,16 +60,32 @@ class WarningMessage: file: TextIO | None line: str | None source: Any | None - def __init__( - self, - message: Warning | str, - category: type[Warning], - filename: str, - lineno: int, - file: TextIO | None = None, - line: str | None = None, - source: Any | None = None, - ) -> None: ... + if sys.version_info >= (3, 15): + module: str | None + if sys.version_info >= (3, 15): + def __init__( + self, + message: Warning | str, + category: type[Warning], + filename: str, + lineno: int, + file: TextIO | None = None, + line: str | None = None, + source: Any | None = None, + module: str | None = None, + ) -> None: ... + + else: + def __init__( + self, + message: Warning | str, + category: type[Warning], + filename: str, + lineno: int, + file: TextIO | None = None, + line: str | None = None, + source: Any | None = None, + ) -> None: ... class catch_warnings(Generic[_W_co]): if sys.version_info >= (3, 11): @@ -76,7 +96,7 @@ class catch_warnings(Generic[_W_co]): record: Literal[False] = False, module: ModuleType | None = None, action: _ActionKind | None = None, - category: type[Warning] = ..., + category: type[Warning] | tuple[type[Warning], ...] = ..., lineno: int = 0, append: bool = False, ) -> None: ... @@ -87,7 +107,7 @@ class catch_warnings(Generic[_W_co]): record: Literal[True], module: ModuleType | None = None, action: _ActionKind | None = None, - category: type[Warning] = ..., + category: type[Warning] | tuple[type[Warning], ...] = ..., lineno: int = 0, append: bool = False, ) -> None: ... @@ -98,7 +118,7 @@ class catch_warnings(Generic[_W_co]): record: bool, module: ModuleType | None = None, action: _ActionKind | None = None, - category: type[Warning] = ..., + category: type[Warning] | tuple[type[Warning], ...] = ..., lineno: int = 0, append: bool = False, ) -> None: ... diff --git a/mypy/typeshed/stdlib/wave.pyi b/mypy/typeshed/stdlib/wave.pyi index fd7dbfade884b..74f8b8a9e3cf7 100644 --- a/mypy/typeshed/stdlib/wave.pyi +++ b/mypy/typeshed/stdlib/wave.pyi @@ -1,15 +1,23 @@ import sys -from _typeshed import ReadableBuffer, Unused -from typing import IO, Any, BinaryIO, Final, Literal, NamedTuple, NoReturn, overload -from typing_extensions import Self, TypeAlias, deprecated +from _typeshed import ReadableBuffer, StrOrBytesPath, Unused +from typing import IO, Any, BinaryIO, Final, Literal, NamedTuple, NoReturn, TypeAlias, overload +from typing_extensions import Self, deprecated __all__ = ["open", "Error", "Wave_read", "Wave_write"] +if sys.version_info >= (3, 15): + __all__ += ["WAVE_FORMAT_PCM", "WAVE_FORMAT_IEEE_FLOAT", "WAVE_FORMAT_EXTENSIBLE"] -_File: TypeAlias = str | IO[bytes] +if sys.version_info >= (3, 15): + _File: TypeAlias = StrOrBytesPath | IO[bytes] +else: + _File: TypeAlias = str | IO[bytes] class Error(Exception): ... WAVE_FORMAT_PCM: Final = 0x0001 +if sys.version_info >= (3, 15): + WAVE_FORMAT_IEEE_FLOAT: Final = 0x0003 + WAVE_FORMAT_EXTENSIBLE: Final = 0xFFFE class _wave_params(NamedTuple): nchannels: int @@ -32,17 +40,17 @@ class Wave_read: def getnframes(self) -> int: ... def getsampwidth(self) -> int: ... def getframerate(self) -> int: ... + if sys.version_info >= (3, 15): + def getformat(self) -> int: ... + def getcomptype(self) -> str: ... def getcompname(self) -> str: ... def getparams(self) -> _wave_params: ... - if sys.version_info >= (3, 13): + if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def getmarkers(self) -> None: ... @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def getmark(self, id: Any) -> NoReturn: ... - else: - def getmarkers(self) -> None: ... - def getmark(self, id: Any) -> NoReturn: ... def setpos(self, pos: int) -> None: ... def readframes(self, nframes: int) -> bytes: ... @@ -58,24 +66,31 @@ class Wave_write: def getsampwidth(self) -> int: ... def setframerate(self, framerate: float) -> None: ... def getframerate(self) -> int: ... + if sys.version_info >= (3, 15): + def setformat(self, format: int) -> None: ... + def getformat(self) -> int: ... + def setnframes(self, nframes: int) -> None: ... def getnframes(self) -> int: ... def setcomptype(self, comptype: str, compname: str) -> None: ... def getcomptype(self) -> str: ... def getcompname(self) -> str: ... - def setparams(self, params: _wave_params | tuple[int, int, int, int, str, str]) -> None: ... + if sys.version_info >= (3, 15): + def setparams( + self, params: _wave_params | tuple[int, int, int, int, str, str] | tuple[int, int, int, int, str, str, int] + ) -> None: ... + else: + def setparams(self, params: _wave_params | tuple[int, int, int, int, str, str]) -> None: ... + def getparams(self) -> _wave_params: ... - if sys.version_info >= (3, 13): + + if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def setmark(self, id: Any, pos: Any, name: Any) -> NoReturn: ... @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def getmark(self, id: Any) -> NoReturn: ... @deprecated("Deprecated since Python 3.13; will be removed in Python 3.15.") def getmarkers(self) -> None: ... - else: - def setmark(self, id: Any, pos: Any, name: Any) -> NoReturn: ... - def getmark(self, id: Any) -> NoReturn: ... - def getmarkers(self) -> None: ... def tell(self) -> int: ... def writeframesraw(self, data: ReadableBuffer) -> None: ... diff --git a/mypy/typeshed/stdlib/weakref.pyi b/mypy/typeshed/stdlib/weakref.pyi index 76ab86b957a13..3308ae42e1cfb 100644 --- a/mypy/typeshed/stdlib/weakref.pyi +++ b/mypy/typeshed/stdlib/weakref.pyi @@ -3,8 +3,8 @@ from _weakref import getweakrefcount as getweakrefcount, getweakrefs as getweakr from _weakrefset import WeakSet as WeakSet from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping from types import GenericAlias -from typing import Any, ClassVar, Generic, TypeVar, final, overload -from typing_extensions import ParamSpec, Self, disjoint_base +from typing import Any, ClassVar, Generic, ParamSpec, TypeVar, final, overload +from typing_extensions import Self, disjoint_base __all__ = [ "ref", @@ -89,6 +89,7 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): /, **kwargs: _VT, ) -> None: ... + def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... def __setitem__(self, key: _KT, value: _VT) -> None: ... @@ -98,12 +99,14 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): def copy(self) -> WeakValueDictionary[_KT, _VT]: ... __copy__ = copy def __deepcopy__(self, memo: Any) -> Self: ... + @overload def get(self, key: _KT, default: None = None) -> _VT | None: ... @overload def get(self, key: _KT, default: _VT) -> _VT: ... @overload def get(self, key: _KT, default: _T) -> _VT | _T: ... + # These are incompatible with Mapping def keys(self) -> Iterator[_KT]: ... # type: ignore[override] def values(self) -> Iterator[_VT]: ... # type: ignore[override] @@ -111,20 +114,24 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): def itervaluerefs(self) -> Iterator[KeyedRef[_KT, _VT]]: ... def valuerefs(self) -> list[KeyedRef[_KT, _VT]]: ... def setdefault(self, key: _KT, default: _VT) -> _VT: ... + @overload def pop(self, key: _KT) -> _VT: ... @overload def pop(self, key: _KT, default: _VT) -> _VT: ... @overload def pop(self, key: _KT, default: _T) -> _VT | _T: ... + @overload def update(self, other: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... @overload def update(self, other: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... @overload def update(self, other: None = None, /, **kwargs: _VT) -> None: ... + def __or__(self, other: Mapping[_T1, _T2]) -> WeakValueDictionary[_KT | _T1, _VT | _T2]: ... def __ror__(self, other: Mapping[_T1, _T2]) -> WeakValueDictionary[_KT | _T1, _VT | _T2]: ... + # WeakValueDictionary.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... @@ -142,6 +149,7 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): def __init__(self, dict: None = None) -> None: ... @overload def __init__(self, dict: Mapping[_KT, _VT] | Iterable[tuple[_KT, _VT]]) -> None: ... + def __len__(self) -> int: ... def __getitem__(self, key: _KT) -> _VT: ... def __setitem__(self, key: _KT, value: _VT) -> None: ... @@ -151,36 +159,43 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): def copy(self) -> WeakKeyDictionary[_KT, _VT]: ... __copy__ = copy def __deepcopy__(self, memo: Any) -> Self: ... + @overload def get(self, key: _KT, default: None = None) -> _VT | None: ... @overload def get(self, key: _KT, default: _VT) -> _VT: ... @overload def get(self, key: _KT, default: _T) -> _VT | _T: ... + # These are incompatible with Mapping def keys(self) -> Iterator[_KT]: ... # type: ignore[override] def values(self) -> Iterator[_VT]: ... # type: ignore[override] def items(self) -> Iterator[tuple[_KT, _VT]]: ... # type: ignore[override] def keyrefs(self) -> list[ref[_KT]]: ... + # Keep WeakKeyDictionary.setdefault in line with MutableMapping.setdefault, modulo positional-only differences @overload def setdefault(self: WeakKeyDictionary[_KT, _VT | None], key: _KT, default: None = None) -> _VT: ... @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... + @overload def pop(self, key: _KT) -> _VT: ... @overload def pop(self, key: _KT, default: _VT) -> _VT: ... @overload def pop(self, key: _KT, default: _T) -> _VT | _T: ... + @overload def update(self, dict: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... @overload def update(self, dict: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... @overload def update(self, dict: None = None, /, **kwargs: _VT) -> None: ... + def __or__(self, other: Mapping[_T1, _T2]) -> WeakKeyDictionary[_KT | _T1, _VT | _T2]: ... def __ror__(self, other: Mapping[_T1, _T2]) -> WeakKeyDictionary[_KT | _T1, _VT | _T2]: ... + # WeakKeyDictionary.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] def __ior__(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> Self: ... diff --git a/mypy/typeshed/stdlib/webbrowser.pyi b/mypy/typeshed/stdlib/webbrowser.pyi index 56c30f8727277..40b57c3c6a097 100644 --- a/mypy/typeshed/stdlib/webbrowser.pyi +++ b/mypy/typeshed/stdlib/webbrowser.pyi @@ -64,16 +64,10 @@ if sys.platform == "win32": if sys.platform == "darwin": if sys.version_info < (3, 13): - if sys.version_info >= (3, 11): - @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") - class MacOSX(BaseBrowser): - def __init__(self, name: str) -> None: ... - def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... - - else: - class MacOSX(BaseBrowser): - def __init__(self, name: str) -> None: ... - def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... + @deprecated("Deprecated since Python 3.11; removed in Python 3.13.") + class MacOSX(BaseBrowser): + def __init__(self, name: str) -> None: ... + def open(self, url: str, new: int = 0, autoraise: bool = True) -> bool: ... class MacOSXOSAScript(BaseBrowser): # In runtime this class does not have `name` and `basename` if sys.version_info >= (3, 11): diff --git a/mypy/typeshed/stdlib/winreg.pyi b/mypy/typeshed/stdlib/winreg.pyi index a654bbcdfb615..8a886112f8a14 100644 --- a/mypy/typeshed/stdlib/winreg.pyi +++ b/mypy/typeshed/stdlib/winreg.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import ReadableBuffer, Unused from types import TracebackType -from typing import Any, Final, Literal, final, overload -from typing_extensions import Self, TypeAlias +from typing import Any, Final, Literal, TypeAlias, final, overload +from typing_extensions import Self if sys.platform == "win32": _KeyType: TypeAlias = HKEYType | int @@ -12,6 +12,9 @@ if sys.platform == "win32": def CreateKeyEx(key: _KeyType, sub_key: str | None, reserved: int = 0, access: int = 131078) -> HKEYType: ... def DeleteKey(key: _KeyType, sub_key: str, /) -> None: ... def DeleteKeyEx(key: _KeyType, sub_key: str, access: int = 256, reserved: int = 0) -> None: ... + if sys.version_info >= (3, 15): + def DeleteTree(key: _KeyType, sub_key: str | None = None, /) -> None: ... + def DeleteValue(key: _KeyType, value: str, /) -> None: ... def EnumKey(key: _KeyType, index: int, /) -> str: ... def EnumValue(key: _KeyType, index: int, /) -> tuple[str, Any, int]: ... @@ -25,6 +28,7 @@ if sys.platform == "win32": def QueryValueEx(key: _KeyType, name: str, /) -> tuple[Any, int]: ... def SaveKey(key: _KeyType, file_name: str, /) -> None: ... def SetValue(key: _KeyType, sub_key: str | None, type: int, value: str, /) -> None: ... + @overload # type=REG_DWORD|REG_QWORD def SetValueEx( key: _KeyType, value_name: str | None, reserved: Unused, type: Literal[4, 5], value: int | None, / @@ -55,6 +59,7 @@ if sys.platform == "win32": value: int | str | list[str] | ReadableBuffer | None, /, ) -> None: ... + def DisableReflectionKey(key: _KeyType, /) -> None: ... def EnableReflectionKey(key: _KeyType, /) -> None: ... def QueryReflectionKey(key: _KeyType, /) -> bool: ... diff --git a/mypy/typeshed/stdlib/winsound.pyi b/mypy/typeshed/stdlib/winsound.pyi index 39dfa7b8b9c42..9c7f314fd6ca4 100644 --- a/mypy/typeshed/stdlib/winsound.pyi +++ b/mypy/typeshed/stdlib/winsound.pyi @@ -30,9 +30,11 @@ if sys.platform == "win32": MB_ICONWARNING: Final = 48 def Beep(frequency: int, duration: int) -> None: ... + # Can actually accept anything ORed with 4, and if not it's definitely str, but that's inexpressible @overload def PlaySound(sound: ReadableBuffer | None, flags: Literal[4]) -> None: ... @overload def PlaySound(sound: str | ReadableBuffer | None, flags: int) -> None: ... + def MessageBeep(type: int = 0) -> None: ... diff --git a/mypy/typeshed/stdlib/wsgiref/headers.pyi b/mypy/typeshed/stdlib/wsgiref/headers.pyi index 9febad4b32775..6a0fb571a0d08 100644 --- a/mypy/typeshed/stdlib/wsgiref/headers.pyi +++ b/mypy/typeshed/stdlib/wsgiref/headers.pyi @@ -1,6 +1,5 @@ from re import Pattern -from typing import Final, overload -from typing_extensions import TypeAlias +from typing import Final, TypeAlias, overload _HeaderList: TypeAlias = list[tuple[str, str]] @@ -14,10 +13,12 @@ class Headers: def __getitem__(self, name: str) -> str | None: ... def __contains__(self, name: str) -> bool: ... def get_all(self, name: str) -> list[str]: ... + @overload def get(self, name: str, default: str) -> str: ... @overload def get(self, name: str, default: str | None = None) -> str | None: ... + def keys(self) -> list[str]: ... def values(self) -> list[str]: ... def items(self) -> _HeaderList: ... diff --git a/mypy/typeshed/stdlib/wsgiref/types.pyi b/mypy/typeshed/stdlib/wsgiref/types.pyi index 57276fd05ea84..b7fd998098568 100644 --- a/mypy/typeshed/stdlib/wsgiref/types.pyi +++ b/mypy/typeshed/stdlib/wsgiref/types.pyi @@ -1,7 +1,6 @@ from _typeshed import OptExcInfo from collections.abc import Callable, Iterable, Iterator -from typing import Any, Protocol -from typing_extensions import TypeAlias +from typing import Any, Protocol, TypeAlias __all__ = ["StartResponse", "WSGIEnvironment", "WSGIApplication", "InputStream", "ErrorStream", "FileWrapper"] diff --git a/mypy/typeshed/stdlib/wsgiref/validate.pyi b/mypy/typeshed/stdlib/wsgiref/validate.pyi index fa8a6bbb8d039..498e03aa36731 100644 --- a/mypy/typeshed/stdlib/wsgiref/validate.pyi +++ b/mypy/typeshed/stdlib/wsgiref/validate.pyi @@ -1,7 +1,6 @@ from _typeshed.wsgi import ErrorStream, InputStream, WSGIApplication from collections.abc import Callable, Iterable, Iterator -from typing import Any, NoReturn -from typing_extensions import TypeAlias +from typing import Any, NoReturn, TypeAlias __all__ = ["validator"] diff --git a/mypy/typeshed/stdlib/xml/__init__.pyi b/mypy/typeshed/stdlib/xml/__init__.pyi index 7a240965136e5..555d9b8f90a95 100644 --- a/mypy/typeshed/stdlib/xml/__init__.pyi +++ b/mypy/typeshed/stdlib/xml/__init__.pyi @@ -1,3 +1,9 @@ # At runtime, listing submodules in __all__ without them being imported is # valid, and causes them to be included in a star import. See #6523 +import sys + __all__ = ["dom", "parsers", "sax", "etree"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] + +if sys.version_info >= (3, 15): + __all__ += ["is_valid_name"] # pyright: ignore[reportUnsupportedDunderAll] + from xml.utils import is_valid_name as is_valid_name, is_valid_text as is_valid_text diff --git a/mypy/typeshed/stdlib/xml/dom/expatbuilder.pyi b/mypy/typeshed/stdlib/xml/dom/expatbuilder.pyi index 2b9ac88769700..e410671829358 100644 --- a/mypy/typeshed/stdlib/xml/dom/expatbuilder.pyi +++ b/mypy/typeshed/stdlib/xml/dom/expatbuilder.pyi @@ -1,6 +1,5 @@ from _typeshed import ReadableBuffer, SupportsRead -from typing import Any, Final, NoReturn -from typing_extensions import TypeAlias +from typing import Any, Final, NoReturn, TypeAlias from xml.dom.minidom import Document, DocumentFragment, DOMImplementation, Element, Node, TypeInfo from xml.dom.xmlbuilder import DOMBuilderFilter, Options from xml.parsers.expat import XMLParserType diff --git a/mypy/typeshed/stdlib/xml/dom/minidom.pyi b/mypy/typeshed/stdlib/xml/dom/minidom.pyi index e0431417aa3c0..c54b0de86e556 100644 --- a/mypy/typeshed/stdlib/xml/dom/minidom.pyi +++ b/mypy/typeshed/stdlib/xml/dom/minidom.pyi @@ -3,8 +3,8 @@ from _collections_abc import dict_keys, dict_values from _typeshed import Incomplete, ReadableBuffer, SupportsRead, SupportsWrite from collections.abc import Iterable, Sequence from types import TracebackType -from typing import Any, ClassVar, Generic, Literal, NoReturn, Protocol, TypeVar, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, ClassVar, Generic, Literal, NoReturn, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing_extensions import Self from xml.dom.minicompat import EmptyNodeList, NodeList from xml.dom.xmlbuilder import DocumentLS, DOMImplementationLS from xml.sax.xmlreader import XMLReader @@ -52,6 +52,7 @@ def parse( file: str | SupportsRead[ReadableBuffer | str], parser: XMLReader | None = None, bufsize: int | None = None ) -> Document: ... def parseString(string: str | ReadableBuffer, parser: XMLReader | None = None) -> Document: ... + @overload def getDOMImplementation(features: None = None) -> DOMImplementation: ... @overload @@ -89,10 +90,12 @@ class Node(xml.dom.Node): @property def localName(self) -> str | None: ... # non-null only for Element and Attr def __bool__(self) -> Literal[True]: ... + @overload def toxml(self, encoding: str, standalone: bool | None = None) -> bytes: ... @overload def toxml(self, encoding: None = None, standalone: bool | None = None) -> str: ... + @overload def toprettyxml( self, @@ -122,6 +125,7 @@ class Node(xml.dom.Node): encoding: str, standalone: bool | None = None, ) -> bytes: ... + def hasChildNodes(self) -> bool: ... def insertBefore( # type: ignore[misc] self: _NodesWithChildren, # pyright: ignore[reportGeneralTypeIssues] @@ -131,6 +135,7 @@ class Node(xml.dom.Node): def appendChild( # type: ignore[misc] self: _NodesWithChildren, node: _ChildNodePlusFragmentVar # pyright: ignore[reportGeneralTypeIssues] ) -> _ChildNodePlusFragmentVar: ... + @overload def replaceChild( # type: ignore[misc] self: _NodesWithChildren, newChild: DocumentFragment, oldChild: _ChildNodeVar @@ -139,6 +144,7 @@ class Node(xml.dom.Node): def replaceChild( # type: ignore[misc] self: _NodesWithChildren, newChild: _NodesThatAreChildren, oldChild: _ChildNodeVar ) -> _ChildNodeVar | None: ... + def removeChild(self: _NodesWithChildren, oldChild: _ChildNodeVar) -> _ChildNodeVar: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] def normalize(self: _NodesWithChildren) -> None: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] def cloneNode(self, deep: bool) -> Self | None: ... @@ -178,10 +184,12 @@ class DocumentFragment(Node): self, newChild: _DFChildrenPlusFragment, refChild: _DocumentFragmentChildren | None ) -> _DFChildrenPlusFragment: ... def appendChild(self, node: _DFChildrenPlusFragment) -> _DFChildrenPlusFragment: ... # type: ignore[override] + @overload # type: ignore[override] def replaceChild(self, newChild: DocumentFragment, oldChild: _DFChildrenVar) -> _DFChildrenVar | DocumentFragment: ... @overload def replaceChild(self, newChild: _DocumentFragmentChildren, oldChild: _DFChildrenVar) -> _DFChildrenVar | None: ... # type: ignore[override] + def removeChild(self, oldChild: _DFChildrenVar) -> _DFChildrenVar: ... # type: ignore[override] _AttrChildrenVar = TypeVar("_AttrChildrenVar", bound=_AttrChildren) @@ -223,10 +231,12 @@ class Attr(Node): def schemaType(self) -> TypeInfo: ... def insertBefore(self, newChild: _AttrChildrenPlusFragment, refChild: _AttrChildren | None) -> _AttrChildrenPlusFragment: ... # type: ignore[override] def appendChild(self, node: _AttrChildrenPlusFragment) -> _AttrChildrenPlusFragment: ... # type: ignore[override] + @overload # type: ignore[override] def replaceChild(self, newChild: DocumentFragment, oldChild: _AttrChildrenVar) -> _AttrChildrenVar | DocumentFragment: ... @overload def replaceChild(self, newChild: _AttrChildren, oldChild: _AttrChildrenVar) -> _AttrChildrenVar | None: ... # type: ignore[override] + def removeChild(self, oldChild: _AttrChildrenVar) -> _AttrChildrenVar: ... # type: ignore[override] # In the DOM, this interface isn't specific to Attr, but our implementation is @@ -339,12 +349,14 @@ class Element(Node): self, newChild: _ElementChildrenPlusFragment, refChild: _ElementChildren | None ) -> _ElementChildrenPlusFragment: ... def appendChild(self, node: _ElementChildrenPlusFragment) -> _ElementChildrenPlusFragment: ... # type: ignore[override] + @overload # type: ignore[override] def replaceChild( self, newChild: DocumentFragment, oldChild: _ElementChildrenVar ) -> _ElementChildrenVar | DocumentFragment: ... @overload def replaceChild(self, newChild: _ElementChildren, oldChild: _ElementChildrenVar) -> _ElementChildrenVar | None: ... # type: ignore[override] + def removeChild(self, oldChild: _ElementChildrenVar) -> _ElementChildrenVar: ... # type: ignore[override] class Childless: @@ -661,15 +673,18 @@ class Document(Node, DocumentLS): encoding: str | None = None, standalone: bool | None = None, ) -> None: ... + @overload def renameNode(self, n: Element, namespaceURI: str, name: str) -> Element: ... @overload def renameNode(self, n: Attr, namespaceURI: str, name: str) -> Attr: ... @overload def renameNode(self, n: Element | Attr, namespaceURI: str, name: str) -> Element | Attr: ... + def insertBefore( self, newChild: _DocumentChildrenPlusFragment, refChild: _DocumentChildren | None # type: ignore[override] ) -> _DocumentChildrenPlusFragment: ... + @overload # type: ignore[override] def replaceChild( self, newChild: DocumentFragment, oldChild: _DocumentChildrenVar diff --git a/mypy/typeshed/stdlib/xml/dom/pulldom.pyi b/mypy/typeshed/stdlib/xml/dom/pulldom.pyi index df7a3ad0eddb0..4ede390155855 100644 --- a/mypy/typeshed/stdlib/xml/dom/pulldom.pyi +++ b/mypy/typeshed/stdlib/xml/dom/pulldom.pyi @@ -1,8 +1,8 @@ import sys from _typeshed import Incomplete, Unused from collections.abc import MutableSequence, Sequence -from typing import Final, Literal, NoReturn -from typing_extensions import Self, TypeAlias +from typing import Final, Literal, NoReturn, TypeAlias +from typing_extensions import Self from xml.dom.minidom import Comment, Document, DOMImplementation, Element, ProcessingInstruction, Text from xml.sax import _SupportsReadClose from xml.sax.handler import ContentHandler diff --git a/mypy/typeshed/stdlib/xml/etree/ElementInclude.pyi b/mypy/typeshed/stdlib/xml/etree/ElementInclude.pyi index 10784e7d40214..5db08fb0df053 100644 --- a/mypy/typeshed/stdlib/xml/etree/ElementInclude.pyi +++ b/mypy/typeshed/stdlib/xml/etree/ElementInclude.pyi @@ -22,6 +22,7 @@ class FatalIncludeError(SyntaxError): ... def default_loader(href: FileDescriptorOrPath, parse: Literal["xml"], encoding: str | None = None) -> Element: ... @overload def default_loader(href: FileDescriptorOrPath, parse: Literal["text"], encoding: str | None = None) -> str: ... + def include(elem: Element, loader: _Loader | None = None, base_url: str | None = None, max_depth: int | None = 6) -> None: ... class LimitedRecursiveIncludeError(FatalIncludeError): ... diff --git a/mypy/typeshed/stdlib/xml/etree/ElementPath.pyi b/mypy/typeshed/stdlib/xml/etree/ElementPath.pyi index 5c03dd014b639..1dd6f86cead63 100644 --- a/mypy/typeshed/stdlib/xml/etree/ElementPath.pyi +++ b/mypy/typeshed/stdlib/xml/etree/ElementPath.pyi @@ -1,7 +1,6 @@ from collections.abc import Callable, Generator, Iterable from re import Pattern -from typing import Any, Final, Literal, TypeVar, overload -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, TypeAlias, TypeVar, overload from xml.etree.ElementTree import Element xpath_tokenizer_re: Final[Pattern[str]] @@ -33,8 +32,10 @@ def iterfind( # type: ignore[overload-overlap] ) -> None: ... @overload def iterfind(elem: Element[Any], path: str, namespaces: dict[str, str] | None = None) -> Generator[Element]: ... + def find(elem: Element[Any], path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... def findall(elem: Element[Any], path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... + @overload def findtext(elem: Element[Any], path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload diff --git a/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi index 6340a44bd51c8..c77af2cb4dd11 100644 --- a/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi +++ b/mypy/typeshed/stdlib/xml/etree/ElementTree.pyi @@ -2,8 +2,8 @@ import sys from _collections_abc import dict_keys from _typeshed import FileDescriptorOrPath, ReadableBuffer, SupportsRead, SupportsWrite from collections.abc import Callable, Generator, ItemsView, Iterable, Iterator, Mapping, Sequence -from typing import Any, Final, Generic, Literal, Protocol, SupportsIndex, TypeVar, overload, type_check_only -from typing_extensions import TypeAlias, TypeGuard, deprecated, disjoint_base +from typing import Any, Final, Generic, Literal, Protocol, SupportsIndex, TypeAlias, TypeGuard, TypeVar, overload, type_check_only +from typing_extensions import deprecated, disjoint_base from xml.parsers.expat import XMLParserType __all__ = [ @@ -27,13 +27,14 @@ __all__ = [ "tostring", "tostringlist", "TreeBuilder", - "VERSION", "XML", "XMLID", "XMLParser", "XMLPullParser", "register_namespace", ] +if sys.version_info < (3, 15): + __all__ += ["VERSION"] _T = TypeVar("_T") _FileRead: TypeAlias = FileDescriptorOrPath | SupportsRead[bytes] | SupportsRead[str] @@ -48,6 +49,7 @@ class ParseError(SyntaxError): # In reality it works based on `.tag` attribute duck typing. def iselement(element: object) -> TypeGuard[Element]: ... + @overload def canonicalize( xml_data: str | ReadableBuffer | None = None, @@ -96,21 +98,26 @@ class Element(Generic[_Tag]): def extend(self, elements: Iterable[Element[Any]], /) -> None: ... def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... + @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... + @overload def get(self, key: str, default: None = None) -> str | None: ... @overload def get(self, key: str, default: _T) -> str | _T: ... + def insert(self, index: int, subelement: Element[Any], /) -> None: ... def items(self) -> ItemsView[str, str]: ... def iter(self, tag: str | None = None) -> Generator[Element]: ... + @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element]: ... + def itertext(self) -> Generator[str]: ... def keys(self) -> dict_keys[str, str]: ... # makeelement returns the type of self in Python impl, but not in C impl @@ -120,13 +127,16 @@ class Element(Generic[_Tag]): def __copy__(self) -> Element[_Tag]: ... # returns the type of self in Python impl, but not in C impl def __deepcopy__(self, memo: Any, /) -> Element: ... # Only exists in C impl def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> Element: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> list[Element]: ... + def __len__(self) -> int: ... # Doesn't actually exist at runtime, but instance of the class are indeed iterable due to __getitem__. def __iter__(self) -> Iterator[Element]: ... + @overload def __setitem__(self, key: SupportsIndex, value: Element[Any], /) -> None: ... @overload @@ -161,15 +171,19 @@ class ElementTree(Generic[_Root]): def parse(self, source: _FileRead, parser: XMLParser | None = None) -> Element: ... def iter(self, tag: str | None = None) -> Generator[Element]: ... def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... + @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... + def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... + @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element]: ... + def write( self, file_or_filename: _FileWrite, @@ -185,6 +199,7 @@ class ElementTree(Generic[_Root]): HTML_EMPTY: Final[set[str]] def register_namespace(prefix: str, uri: str) -> None: ... + @overload def tostring( element: Element[Any], @@ -215,6 +230,7 @@ def tostring( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> Any: ... + @overload def tostringlist( element: Element[Any], @@ -245,32 +261,47 @@ def tostringlist( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> list[Any]: ... + def dump(elem: Element[Any] | ElementTree[Any]) -> None: ... def indent(tree: Element[Any] | ElementTree[Any], space: str = " ", level: int = 0) -> None: ... def parse(source: _FileRead, parser: XMLParser[Any] | None = None) -> ElementTree[Element]: ... -# This class is defined inside the body of iterparse +# The type of the second element of the tuple yielded by iterparse depends +# on the event type in the first element of the tuple: +# * start, end: Element[str] +# * comment, pi: Element[_ElementCallable] +# * start-ns: tuple[str, str] (prefix, uri) +# * end-ns: None +_EventT_co = TypeVar("_EventT_co", bound=Element[str] | Element[_ElementCallable] | tuple[str, str] | None, covariant=True) +_EventType: TypeAlias = Literal["start", "end", "comment", "pi", "start-ns", "end-ns"] + +# This class is defined inside the body of iterparse. @type_check_only -class _IterParseIterator(Iterator[tuple[str, Element]], Protocol): - def __next__(self) -> tuple[str, Element]: ... +class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_EventT_co]): if sys.version_info >= (3, 13): def close(self) -> None: ... if sys.version_info >= (3, 11): def __del__(self) -> None: ... +# See the comment for _EventT_co above for possible iterator types. @overload -def iterparse(source: _FileRead, events: Sequence[str] | None = None) -> _IterParseIterator: ... +def iterparse(source: _FileRead, events: Iterable[_EventType]) -> _IterParseIterator[Any]: ... +@overload +def iterparse(source: _FileRead, events: None = None) -> _IterParseIterator[Element[str]]: ... + +# In case a custom parser is passed, the type of the second element of the tuple +# yielded by iterparse depends on the parser. @overload @deprecated("The `parser` parameter is deprecated since Python 3.4.") -def iterparse(source: _FileRead, events: Sequence[str] | None = None, parser: XMLParser | None = None) -> _IterParseIterator: ... +def iterparse(source: _FileRead, events: Iterable[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... _EventQueue: TypeAlias = tuple[str] | tuple[str, tuple[str, str]] | tuple[str, None] -class XMLPullParser(Generic[_E]): - def __init__(self, events: Sequence[str] | None = None, *, _parser: XMLParser[_E] | None = None) -> None: ... +class XMLPullParser(Generic[_EventT_co]): + def __init__(self, events: Iterable[_EventType] | None = None, *, _parser: XMLParser[_EventT_co] | None = None) -> None: ... def feed(self, data: str | ReadableBuffer) -> None: ... def close(self) -> None: ... - def read_events(self) -> Iterator[_EventQueue | tuple[str, _E]]: ... + def read_events(self) -> Iterator[_EventQueue | tuple[_EventType, _EventT_co]]: ... def flush(self) -> None: ... def XML(text: str | ReadableBuffer, parser: XMLParser | None = None) -> Element: ... diff --git a/mypy/typeshed/stdlib/xml/sax/__init__.pyi b/mypy/typeshed/stdlib/xml/sax/__init__.pyi index 679466fa34d2c..9b5c3bf4ddea9 100644 --- a/mypy/typeshed/stdlib/xml/sax/__init__.pyi +++ b/mypy/typeshed/stdlib/xml/sax/__init__.pyi @@ -1,8 +1,7 @@ import sys from _typeshed import ReadableBuffer, StrPath, SupportsRead, _T_co from collections.abc import Iterable -from typing import Final, Protocol, type_check_only -from typing_extensions import TypeAlias +from typing import Final, Protocol, TypeAlias, type_check_only from xml.sax._exceptions import ( SAXException as SAXException, SAXNotRecognizedException as SAXNotRecognizedException, diff --git a/mypy/typeshed/stdlib/xml/sax/expatreader.pyi b/mypy/typeshed/stdlib/xml/sax/expatreader.pyi index 3f9573a25f9aa..e29853ebc3fe3 100644 --- a/mypy/typeshed/stdlib/xml/sax/expatreader.pyi +++ b/mypy/typeshed/stdlib/xml/sax/expatreader.pyi @@ -1,13 +1,8 @@ -import sys from _typeshed import ReadableBuffer from collections.abc import Mapping -from typing import Any, Final, Literal, overload -from typing_extensions import TypeAlias +from typing import Any, Final, Literal, TypeAlias, overload from xml.sax import _Source, xmlreader -from xml.sax.handler import _ContentHandlerProtocol - -if sys.version_info >= (3, 10): - from xml.sax.handler import LexicalHandler +from xml.sax.handler import LexicalHandler, _ContentHandlerProtocol _BoolType: TypeAlias = Literal[0, 1] | bool @@ -33,26 +28,25 @@ class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator): def setContentHandler(self, handler: _ContentHandlerProtocol) -> None: ... def getFeature(self, name: str) -> _BoolType: ... def setFeature(self, name: str, state: _BoolType) -> None: ... - if sys.version_info >= (3, 10): - @overload - def getProperty(self, name: Literal["http://xml.org/sax/properties/lexical-handler"]) -> LexicalHandler | None: ... + @overload + def getProperty(self, name: Literal["http://xml.org/sax/properties/lexical-handler"]) -> LexicalHandler | None: ... @overload def getProperty(self, name: Literal["http://www.python.org/sax/properties/interning-dict"]) -> dict[str, Any] | None: ... @overload def getProperty(self, name: Literal["http://xml.org/sax/properties/xml-string"]) -> bytes | None: ... @overload def getProperty(self, name: str) -> object: ... - if sys.version_info >= (3, 10): - @overload - def setProperty(self, name: Literal["http://xml.org/sax/properties/lexical-handler"], value: LexicalHandler) -> None: ... + @overload + def setProperty(self, name: Literal["http://xml.org/sax/properties/lexical-handler"], value: LexicalHandler) -> None: ... @overload def setProperty( self, name: Literal["http://www.python.org/sax/properties/interning-dict"], value: dict[str, Any] ) -> None: ... @overload def setProperty(self, name: str, value: object) -> None: ... + def feed(self, data: str | ReadableBuffer, isFinal: bool = False) -> None: ... def flush(self) -> None: ... def close(self) -> None: ... diff --git a/mypy/typeshed/stdlib/xml/sax/handler.pyi b/mypy/typeshed/stdlib/xml/sax/handler.pyi index 5ecbfa6f1272c..e1e080ac2d1aa 100644 --- a/mypy/typeshed/stdlib/xml/sax/handler.pyi +++ b/mypy/typeshed/stdlib/xml/sax/handler.pyi @@ -1,4 +1,3 @@ -import sys from typing import Final, NoReturn, Protocol, type_check_only from xml.sax import xmlreader @@ -77,10 +76,9 @@ property_encoding: Final = "http://www.python.org/sax/properties/encoding" property_interning_dict: Final[str] # too long string all_properties: Final[list[str]] -if sys.version_info >= (3, 10): - class LexicalHandler: - def comment(self, content: str) -> None: ... - def startDTD(self, name: str, public_id: str | None, system_id: str | None) -> None: ... - def endDTD(self) -> None: ... - def startCDATA(self) -> None: ... - def endCDATA(self) -> None: ... +class LexicalHandler: + def comment(self, content: str) -> None: ... + def startDTD(self, name: str, public_id: str | None, system_id: str | None) -> None: ... + def endDTD(self) -> None: ... + def startCDATA(self) -> None: ... + def endCDATA(self) -> None: ... diff --git a/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi b/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi index e7d04ddeadb80..a7ae5edc55d35 100644 --- a/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi +++ b/mypy/typeshed/stdlib/xml/sax/xmlreader.pyi @@ -1,7 +1,7 @@ from _typeshed import ReadableBuffer from collections.abc import Mapping -from typing import Generic, Literal, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing import Generic, Literal, TypeAlias, TypeVar, overload +from typing_extensions import Self from xml.sax import _Source, _SupportsReadClose from xml.sax.handler import _ContentHandlerProtocol, _DTDHandlerProtocol, _EntityResolverProtocol, _ErrorHandlerProtocol @@ -64,10 +64,12 @@ class AttributesImpl(Generic[_AttrKey]): def __getitem__(self, name: _AttrKey) -> str: ... def keys(self) -> list[_AttrKey]: ... def __contains__(self, name: _AttrKey) -> bool: ... + @overload def get(self, name: _AttrKey, alternative: None = None) -> str | None: ... @overload def get(self, name: _AttrKey, alternative: str) -> str: ... + def copy(self) -> Self: ... def items(self) -> list[tuple[_AttrKey, str]]: ... def values(self) -> list[str]: ... @@ -83,8 +85,10 @@ class AttributesNSImpl(AttributesImpl[_NSName]): def __getitem__(self, name: _NSName) -> str: ... def keys(self) -> list[_NSName]: ... def __contains__(self, name: _NSName) -> bool: ... + @overload def get(self, name: _NSName, alternative: None = None) -> str | None: ... @overload def get(self, name: _NSName, alternative: str) -> str: ... + def items(self) -> list[tuple[_NSName, str]]: ... diff --git a/mypy/typeshed/stdlib/xml/utils.pyi b/mypy/typeshed/stdlib/xml/utils.pyi new file mode 100644 index 0000000000000..1c3bb877a7ccf --- /dev/null +++ b/mypy/typeshed/stdlib/xml/utils.pyi @@ -0,0 +1,2 @@ +def is_valid_name(name: str) -> bool: ... +def is_valid_text(data: str) -> bool: ... diff --git a/mypy/typeshed/stdlib/xmlrpc/client.pyi b/mypy/typeshed/stdlib/xmlrpc/client.pyi index 42420ee85848f..573401f18d082 100644 --- a/mypy/typeshed/stdlib/xmlrpc/client.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/client.pyi @@ -6,8 +6,8 @@ from collections.abc import Callable, Iterable, Mapping from datetime import datetime from io import BytesIO from types import TracebackType -from typing import Any, ClassVar, Final, Literal, Protocol, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import Any, ClassVar, Final, Literal, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self @type_check_only class _SupportsTimeTuple(Protocol): @@ -282,12 +282,14 @@ class ServerProxy: context: Any | None = None, ) -> None: ... def __getattr__(self, name: str) -> _Method: ... + @overload def __call__(self, attr: Literal["close"]) -> Callable[[], None]: ... @overload def __call__(self, attr: Literal["transport"]) -> Transport: ... @overload def __call__(self, attr: str) -> Callable[[], None] | Transport: ... + def __enter__(self) -> Self: ... def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None diff --git a/mypy/typeshed/stdlib/xmlrpc/server.pyi b/mypy/typeshed/stdlib/xmlrpc/server.pyi index 286aaf980fbf5..7bf397d2b1a43 100644 --- a/mypy/typeshed/stdlib/xmlrpc/server.pyi +++ b/mypy/typeshed/stdlib/xmlrpc/server.pyi @@ -4,8 +4,7 @@ import socketserver from _typeshed import ReadableBuffer from collections.abc import Callable, Iterable, Mapping from re import Pattern -from typing import Any, ClassVar, Protocol, type_check_only -from typing_extensions import TypeAlias +from typing import Any, ClassVar, Protocol, TypeAlias, type_check_only from xmlrpc.client import Fault, _Marshallable # The dispatch accepts anywhere from 0 to N arguments, no easy way to allow this in mypy diff --git a/mypy/typeshed/stdlib/xxlimited.pyi b/mypy/typeshed/stdlib/xxlimited.pyi index 78a50b85f405a..503caf0183f31 100644 --- a/mypy/typeshed/stdlib/xxlimited.pyi +++ b/mypy/typeshed/stdlib/xxlimited.pyi @@ -1,5 +1,5 @@ import sys -from typing import Any, ClassVar, final +from typing import Any, final class Str(str): ... @@ -12,13 +12,4 @@ class Xxo: def foo(i: int, j: int, /) -> Any: ... def new() -> Xxo: ... -if sys.version_info >= (3, 10): - class Error(Exception): ... - -else: - class error(Exception): ... - - class Null: - __hash__: ClassVar[None] # type: ignore[assignment] - - def roj(b: Any, /) -> None: ... +class Error(Exception): ... diff --git a/mypy/typeshed/stdlib/zipapp.pyi b/mypy/typeshed/stdlib/zipapp.pyi index c7cf1704b1359..48713bced8921 100644 --- a/mypy/typeshed/stdlib/zipapp.pyi +++ b/mypy/typeshed/stdlib/zipapp.pyi @@ -1,7 +1,6 @@ from collections.abc import Callable from pathlib import Path -from typing import BinaryIO -from typing_extensions import TypeAlias +from typing import BinaryIO, TypeAlias __all__ = ["ZipAppError", "create_archive", "get_interpreter"] diff --git a/mypy/typeshed/stdlib/zipfile/__init__.pyi b/mypy/typeshed/stdlib/zipfile/__init__.pyi index 19d8117a621fc..0039a05b5d6fe 100644 --- a/mypy/typeshed/stdlib/zipfile/__init__.pyi +++ b/mypy/typeshed/stdlib/zipfile/__init__.pyi @@ -5,8 +5,8 @@ from collections.abc import Callable, Iterable, Iterator from io import TextIOWrapper from os import PathLike from types import TracebackType -from typing import IO, Final, Literal, Protocol, overload, type_check_only -from typing_extensions import Self, TypeAlias +from typing import IO, Final, Literal, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import Self __all__ = [ "BadZipFile", @@ -67,6 +67,7 @@ class ZipExtFile(io.BufferedIOBase): newlines: list[bytes] | None mode: _ReadWriteMode name: str + @overload def __init__( self, fileobj: _ClosableZipStream, mode: _ReadWriteMode, zipinfo: ZipInfo, pwd: bytes | None, close_fileobj: Literal[True] @@ -90,6 +91,7 @@ class ZipExtFile(io.BufferedIOBase): pwd: bytes | None = None, close_fileobj: Literal[False] = False, ) -> None: ... + def read(self, n: int | None = -1) -> bytes: ... def readline(self, limit: int = -1) -> bytes: ... # type: ignore[override] def peek(self, n: int = 1) -> bytes: ... @@ -330,6 +332,7 @@ if sys.version_info >= (3, 12): else: class CompleteDirs(ZipFile): def resolve_dir(self, name: str) -> str: ... + @overload @classmethod def make(cls, source: ZipFile) -> CompleteDirs: ... @@ -345,9 +348,8 @@ else: def name(self) -> str: ... @property def parent(self) -> PathLike[str]: ... # undocumented - if sys.version_info >= (3, 10): - @property - def filename(self) -> PathLike[str]: ... # undocumented + @property + def filename(self) -> PathLike[str]: ... # undocumented if sys.version_info >= (3, 11): @property def suffix(self) -> str: ... @@ -371,11 +373,7 @@ else: @overload def open(self, mode: Literal["rb", "wb"], *, pwd: bytes | None = None) -> IO[bytes]: ... - if sys.version_info >= (3, 10): - def iterdir(self) -> Iterator[Self]: ... - else: - def iterdir(self) -> Iterator[Path]: ... - + def iterdir(self) -> Iterator[Self]: ... def is_dir(self) -> bool: ... def is_file(self) -> bool: ... def exists(self) -> bool: ... @@ -388,11 +386,7 @@ else: write_through: bool = False, ) -> str: ... def read_bytes(self) -> bytes: ... - if sys.version_info >= (3, 10): - def joinpath(self, *other: StrPath) -> Path: ... - else: - def joinpath(self, add: StrPath) -> Path: ... # undocumented - + def joinpath(self, *other: StrPath) -> Path: ... def __truediv__(self, add: StrPath) -> Path: ... def is_zipfile(filename: StrOrBytesPath | _SupportsReadSeekTell) -> bool: ... diff --git a/mypy/typeshed/stdlib/zipfile/_path/__init__.pyi b/mypy/typeshed/stdlib/zipfile/_path/__init__.pyi index c936c4494c7cb..e1449dc681ad5 100644 --- a/mypy/typeshed/stdlib/zipfile/_path/__init__.pyi +++ b/mypy/typeshed/stdlib/zipfile/_path/__init__.pyi @@ -19,12 +19,14 @@ if sys.version_info >= (3, 12): class CompleteDirs(InitializedState, ZipFile): def resolve_dir(self, name: str) -> str: ... + @overload @classmethod def make(cls, source: ZipFile) -> CompleteDirs: ... @overload @classmethod def make(cls, source: StrPath | IO[bytes]) -> Self: ... + if sys.version_info >= (3, 13): @classmethod def inject(cls, zf: _ZF) -> _ZF: ... @@ -45,6 +47,7 @@ if sys.version_info >= (3, 12): def suffixes(self) -> list[str]: ... @property def stem(self) -> str: ... + @overload def open( self, @@ -59,6 +62,7 @@ if sys.version_info >= (3, 12): ) -> TextIOWrapper: ... @overload def open(self, mode: Literal["rb", "wb"], *, pwd: bytes | None = None) -> IO[bytes]: ... + def iterdir(self) -> Iterator[Self]: ... def is_dir(self) -> bool: ... def is_file(self) -> bool: ... diff --git a/mypy/typeshed/stdlib/zipimport.pyi b/mypy/typeshed/stdlib/zipimport.pyi index 22af3c272759b..4b34f1f2ad3e7 100644 --- a/mypy/typeshed/stdlib/zipimport.pyi +++ b/mypy/typeshed/stdlib/zipimport.pyi @@ -1,19 +1,11 @@ import sys +from _frozen_importlib_external import _LoaderBasics from _typeshed import StrOrBytesPath from importlib.machinery import ModuleSpec +from importlib.readers import ZipReader from types import CodeType, ModuleType from typing_extensions import deprecated -if sys.version_info >= (3, 10): - from importlib.readers import ZipReader -else: - from importlib.abc import ResourceReader - -if sys.version_info >= (3, 10): - from _frozen_importlib_external import _LoaderBasics -else: - _LoaderBasics = object - __all__ = ["ZipImportError", "zipimporter"] class ZipImportError(ImportError): ... @@ -27,33 +19,26 @@ class zipimporter(_LoaderBasics): def __init__(self, path: StrOrBytesPath) -> None: ... if sys.version_info < (3, 12): - if sys.version_info >= (3, 10): - @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `find_spec()` instead.") - def find_loader(self, fullname: str, path: str | None = None) -> tuple[zipimporter | None, list[str]]: ... - @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `find_spec()` instead.") - def find_module(self, fullname: str, path: str | None = None) -> zipimporter | None: ... - else: - def find_loader(self, fullname: str, path: str | None = None) -> tuple[zipimporter | None, list[str]]: ... - def find_module(self, fullname: str, path: str | None = None) -> zipimporter | None: ... + @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `find_spec()` instead.") + def find_loader(self, fullname: str, path: str | None = None) -> tuple[zipimporter | None, list[str]]: ... + @deprecated("Deprecated since Python 3.10; removed in Python 3.12. Use `find_spec()` instead.") + def find_module(self, fullname: str, path: str | None = None) -> zipimporter | None: ... def get_code(self, fullname: str) -> CodeType: ... def get_data(self, pathname: str) -> bytes: ... def get_filename(self, fullname: str) -> str: ... if sys.version_info >= (3, 14): def get_resource_reader(self, fullname: str) -> ZipReader: ... # undocumented - elif sys.version_info >= (3, 10): - def get_resource_reader(self, fullname: str) -> ZipReader | None: ... # undocumented else: - def get_resource_reader(self, fullname: str) -> ResourceReader | None: ... # undocumented + def get_resource_reader(self, fullname: str) -> ZipReader | None: ... # undocumented def get_source(self, fullname: str) -> str | None: ... def is_package(self, fullname: str) -> bool: ... - if sys.version_info >= (3, 10): + if sys.version_info < (3, 15): @deprecated("Deprecated since Python 3.10; removed in Python 3.15. Use `exec_module()` instead.") def load_module(self, fullname: str) -> ModuleType: ... - def exec_module(self, module: ModuleType) -> None: ... - def create_module(self, spec: ModuleSpec) -> None: ... - def find_spec(self, fullname: str, target: ModuleType | None = None) -> ModuleSpec | None: ... - def invalidate_caches(self) -> None: ... - else: - def load_module(self, fullname: str) -> ModuleType: ... + + def exec_module(self, module: ModuleType) -> None: ... + def create_module(self, spec: ModuleSpec) -> None: ... + def find_spec(self, fullname: str, target: ModuleType | None = None) -> ModuleSpec | None: ... + def invalidate_caches(self) -> None: ... diff --git a/mypy/typeshed/stdlib/zlib.pyi b/mypy/typeshed/stdlib/zlib.pyi index d5998cab90fef..557fa761feebf 100644 --- a/mypy/typeshed/stdlib/zlib.pyi +++ b/mypy/typeshed/stdlib/zlib.pyi @@ -60,6 +60,9 @@ class _Decompress: def adler32(data: ReadableBuffer, value: int = 1, /) -> int: ... +if sys.version_info >= (3, 15): + def adler32_combine(adler1: int, adler2: int, len2: int, /) -> int: ... + if sys.version_info >= (3, 11): def compress(data: ReadableBuffer, /, level: int = -1, wbits: int = 15) -> bytes: ... @@ -70,5 +73,9 @@ def compressobj( level: int = -1, method: int = 8, wbits: int = 15, memLevel: int = 8, strategy: int = 0, zdict: ReadableBuffer | None = None ) -> _Compress: ... def crc32(data: ReadableBuffer, value: int = 0, /) -> int: ... + +if sys.version_info >= (3, 15): + def crc32_combine(crc1: int, crc2: int, len2: int, /) -> int: ... + def decompress(data: ReadableBuffer, /, wbits: int = 15, bufsize: int = 16384) -> bytes: ... def decompressobj(wbits: int = 15, zdict: ReadableBuffer = b"") -> _Decompress: ... diff --git a/mypy/typeshed/stdlib/zoneinfo/__init__.pyi b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi index b7433f835f83d..def31546becbf 100644 --- a/mypy/typeshed/stdlib/zoneinfo/__init__.pyi +++ b/mypy/typeshed/stdlib/zoneinfo/__init__.pyi @@ -19,6 +19,7 @@ class ZoneInfo(tzinfo): def __new__(cls, key: str) -> Self: ... @classmethod def no_cache(cls, key: str) -> Self: ... + if sys.version_info >= (3, 12): @classmethod def from_file(cls, file_obj: _IOBytes, /, key: str | None = None) -> Self: ... diff --git a/mypy/typeshed/stubs/librt/librt/random.pyi b/mypy/typeshed/stubs/librt/librt/random.pyi new file mode 100644 index 0000000000000..d1330aa56faf1 --- /dev/null +++ b/mypy/typeshed/stubs/librt/librt/random.pyi @@ -0,0 +1,22 @@ +from typing import final, overload + +from mypy_extensions import i64 + +def random() -> float: ... +def randint(a: i64, b: i64) -> i64: ... +@overload +def randrange(stop: i64, /) -> i64: ... +@overload +def randrange(start: i64, stop: i64, /) -> i64: ... +def seed(n: i64, /) -> None: ... + +@final +class Random: + def __init__(self, seed: i64 | None = None) -> None: ... + def randint(self, a: i64, b: i64) -> i64: ... + @overload + def randrange(self, stop: i64, /) -> i64: ... + @overload + def randrange(self, start: i64, stop: i64, /) -> i64: ... + def random(self) -> float: ... + def seed(self, n: i64, /) -> None: ... diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 711e52c2e3700..af9ae60936d8e 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -40,3 +40,22 @@ def write_f64_le(b: BytesWriter, n: float, /) -> None: ... def write_f64_be(b: BytesWriter, n: float, /) -> None: ... def read_f64_le(b: bytes, index: i64, /) -> float: ... def read_f64_be(b: bytes, index: i64, /) -> float: ... + +# Codepoint classification helpers operating on i32 codepoints (typically +# obtained via ord(s[i])). Out-of-range inputs (negative, or past the maximum +# Unicode code point 0x10FFFF) return False. +def isspace(c: i32, /) -> bool: ... +def isdigit(c: i32, /) -> bool: ... +def isalnum(c: i32, /) -> bool: ... +def isalpha(c: i32, /) -> bool: ... +def isidentifier(c: i32, /) -> bool: ... + +# Codepoint case conversion. For the rare codepoints whose Unicode +# uppercase / lowercase expands to multiple codepoints (e.g. U+00DF +# uppercases to "SS", U+FB01 to "FI"), returns the input unchanged so +# the signature stays i32 -> i32. Use str.upper() / str.lower() for full +# Unicode case conversion when those cases matter. Out-of-range inputs +# (negative, or past the maximum Unicode code point 0x10FFFF) are returned +# unchanged. +def toupper(c: i32, /) -> i32: ... +def tolower(c: i32, /) -> i32: ... diff --git a/mypy/typeshed/stubs/librt/librt/threading.pyi b/mypy/typeshed/stubs/librt/librt/threading.pyi new file mode 100644 index 0000000000000..a3571ac7f4745 --- /dev/null +++ b/mypy/typeshed/stubs/librt/librt/threading.pyi @@ -0,0 +1,16 @@ +from types import TracebackType +from typing import final + +@final +class Lock: + def acquire(self, blocking: bool = True) -> bool: ... + def release(self) -> None: ... + def locked(self) -> bool: ... + def __enter__(self) -> bool: ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + /, + ) -> None: ... diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index abd0f6bf3bdfe..9cbf9d95067a9 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -82,7 +82,11 @@ def visit_instance(self, t: Instance, /) -> None: self.traverse_type_tuple(t.args) def visit_callable_type(self, t: CallableType, /) -> None: - # FIX generics + for tv in t.variables: + tv.upper_bound.accept(self) + if isinstance(tv, TypeVarType): + for v in tv.values: + v.accept(self) self.traverse_type_list(t.arg_types) t.ret_type.accept(self) t.fallback.accept(self) @@ -93,6 +97,9 @@ def visit_callable_type(self, t: CallableType, /) -> None: if t.type_is is not None: t.type_is.accept(self) + if t.instance_type is not None: + t.instance_type.accept(self) + def visit_tuple_type(self, t: TupleType, /) -> None: self.traverse_type_list(t.items) t.partial_fallback.accept(self) diff --git a/mypy/version.py b/mypy/version.py index 82a0d52db14f7..dd5800e0daced 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "2.1.0+dev" +__version__ = "2.3.0" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypyc/analysis/attrdefined.py b/mypyc/analysis/attrdefined.py index 1dfd33630f1c0..bfb9e7652a4f8 100644 --- a/mypyc/analysis/attrdefined.py +++ b/mypyc/analysis/attrdefined.py @@ -424,14 +424,20 @@ def detect_undefined_bitmap(cl: ClassIR, seen: set[ClassIR]) -> None: for base in cl.base_mro[1:]: detect_undefined_bitmap(base, seen) + # Build fresh and assign once. This function is called per SCC and `seen` + # only dedupes within a single call, so appending in place to a shared base + # would accumulate duplicates across SCCs and produce non-deterministic + # struct layouts under separate=True. + new_attrs: list[str] = [] if len(cl.base_mro) > 1: - cl.bitmap_attrs.extend(cl.base_mro[1].bitmap_attrs) + new_attrs.extend(cl.base_mro[1].bitmap_attrs) for n, t in cl.attributes.items(): if t.error_overlap and not cl.is_always_defined(n): - cl.bitmap_attrs.append(n) + new_attrs.append(n) for base in cl.mro[1:]: if base.is_trait: for n, t in base.attributes.items(): - if t.error_overlap and not cl.is_always_defined(n) and n not in cl.bitmap_attrs: - cl.bitmap_attrs.append(n) + if t.error_overlap and not cl.is_always_defined(n) and n not in new_attrs: + new_attrs.append(n) + cl.bitmap_attrs = new_attrs diff --git a/mypyc/build.py b/mypyc/build.py index d55334d8ac800..8c6eabead17c9 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -54,7 +54,12 @@ class ModDesc(NamedTuple): LIBRT_MODULES = [ ModDesc("librt.internal", ["internal/librt_internal.c"], [], ["internal"]), - ModDesc("librt.strings", ["strings/librt_strings.c"], [], ["strings"]), + ModDesc( + "librt.strings", + ["strings/librt_strings.c"], + ["strings/librt_strings.h", "strings/librt_strings_common.h"], + ["strings"], + ), ModDesc( "librt.base64", [ @@ -121,6 +126,13 @@ class ModDesc(NamedTuple): ["vecs"], ), ModDesc("librt.time", ["time/librt_time.c"], ["time/librt_time.h"], []), + ModDesc("librt.random", ["random/librt_random.c"], ["random/librt_random.h"], ["random"]), + ModDesc( + "librt.threading", + ["threading/librt_threading.c"], + ["threading/librt_threading.h"], + ["threading"], + ), ] try: @@ -322,34 +334,36 @@ def generate_c( emit_messages(options, e.messages, time.time() - t0, serious=(not e.use_stdout)) sys.exit(1) - t1 = time.time() - if result.errors: - emit_messages(options, result.errors, t1 - t0) - sys.exit(1) - - if compiler_options.verbose: - print(f"Parsed and typechecked in {t1 - t0:.3f}s") + try: + t1 = time.time() + if result.errors: + emit_messages(options, result.errors, t1 - t0) + sys.exit(1) - errors = Errors(options) - modules, ctext, mapper = emitmodule.compile_modules_to_c( - result, compiler_options=compiler_options, errors=errors, groups=groups - ) - t2 = time.time() - emit_messages(options, errors.new_messages(), t2 - t1) - if errors.num_errors: - # No need to stop the build if only warnings were emitted. - sys.exit(1) + if compiler_options.verbose: + print(f"Parsed and typechecked in {t1 - t0:.3f}s") - if compiler_options.verbose: - print(f"Compiled to C in {t2 - t1:.3f}s") + errors = Errors(options) + modules, ctext, mapper = emitmodule.compile_modules_to_c( + result, compiler_options=compiler_options, errors=errors, groups=groups + ) + t2 = time.time() + emit_messages(options, errors.new_messages(), t2 - t1) + if errors.num_errors: + # No need to stop the build if only warnings were emitted. + sys.exit(1) - if options.mypyc_annotation_file: - generate_annotated_html(options.mypyc_annotation_file, result, modules, mapper) + if compiler_options.verbose: + print(f"Compiled to C in {t2 - t1:.3f}s") - # Collect SourceDep dependencies - source_deps = sorted(emitmodule.collect_source_dependencies(modules), key=lambda d: d.path) + if options.mypyc_annotation_file: + generate_annotated_html(options.mypyc_annotation_file, result, modules, mapper) - return ctext, "\n".join(format_modules(modules)), source_deps + # Collect SourceDep dependencies + source_deps = sorted(emitmodule.collect_source_dependencies(modules), key=lambda d: d.path) + return ctext, "\n".join(format_modules(modules)), source_deps + finally: + result.manager.metastore.close() def build_using_shared_lib( @@ -449,6 +463,70 @@ def write_file(path: str, contents: str) -> None: os.utime(path, times=(new_mtime, new_mtime)) +_MYPYC_EXTENSION_MARKER = "_mypyc_skip_redundant_inplace_copy" +_setuptools_patch_applied = False + + +def _patch_setuptools_copy_extensions_to_source() -> None: + """Skip redundant `.so` copies for extensions we generated. + + setuptools' copy_extensions_to_source rewrites every `.so` in the + source tree on every build_ext, even when nothing changed. On macOS + this invalidates AMFI's signature cache (~100 ms re-verification per + `.so` on the next import), eating most of the separate=True + incremental speedup. + + The patch is global because copy_extensions_to_source runs during + setup()'s build_ext command, after mypycify() has already returned; + we can't scope a context manager around it. Instead the skip only + fires for extensions tagged by mypycify (via the marker attribute), + so other setuptools users in the same setup.py see the unmodified + upstream behavior, including stub writes. + """ + global _setuptools_patch_applied + if _setuptools_patch_applied: + return + _setuptools_patch_applied = True + + from setuptools.command.build_ext import build_ext as _build_ext + + original = _build_ext.copy_extensions_to_source + + def _files_match(a: str, b: str) -> bool: + try: + sa = os.stat(a) + sb = os.stat(b) + except OSError: + return False + # Compare size + whole-second mtime. distutils' copy_file + # propagates the source mtime, but macOS drops sub-second + # precision on write so the float values never match verbatim. + return sa.st_size == sb.st_size and int(sa.st_mtime) == int(sb.st_mtime) + + def patched(self: Any) -> None: + build_py = self.get_finalized_command("build_py") + + def is_redundant(ext: Any) -> bool: + if not getattr(ext, _MYPYC_EXTENSION_MARKER, False): + return False + inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext) + return _files_match(regular_file, inplace_file) + + # Hide our already-fresh extensions from setuptools' loop and + # let it handle whatever's left. Delegating instead of + # reimplementing the body means future setuptools changes carry + # over for free. self.extensions is restored before we return + # so anything that inspects it later sees the original list. + saved = self.extensions + self.extensions = [ext for ext in saved if not is_redundant(ext)] + try: + original(self) + finally: + self.extensions = saved + + _build_ext.copy_extensions_to_source = patched # type: ignore[method-assign] + + def construct_groups( sources: list[BuildSource], separate: bool | list[tuple[list[str], str | None]], @@ -501,22 +579,115 @@ def construct_groups( return groups -def get_header_deps(cfiles: list[tuple[str, str]]) -> list[str]: - """Find all the headers used by a group of cfiles. +# Single regex that captures both `#include "foo"` and `#include `. The +# alternation lets us tell the two forms apart: the quoted-form match populates +# group 1 and the angle-form match populates group 2. The C preprocessor +# applies different search rules to each kind (see `_extract_includes`), so we +# carry the kind through resolution rather than collapsing them up front. +_INCLUDE_RE = re.compile(r'#\s*include\s+(?:"([^"]+)"|<([^>]+)>)') + + +def _extract_includes(contents: str) -> list[tuple[bool, str]]: + """Return each `#include` directive's (is_angled, name) from `contents`. + + is_angled=False for `#include "foo"`, True for `#include `. + """ + out: list[tuple[bool, str]] = [] + for quoted, angled in _INCLUDE_RE.findall(contents): + if quoted: + out.append((False, quoted)) + else: + out.append((True, angled)) + return out + + +def get_header_deps(cfiles: list[tuple[str, str]]) -> list[tuple[bool, str]]: + """Find all the headers directly included by a group of cfiles. + + Returns a sorted, deduplicated list of `(is_angled, header_name)` pairs. + Callers that only need the names can ignore the bool, but it is needed by + `resolve_cfile_deps` to apply the correct preprocessor search order. We do this by just regexping the source, which is a bit simpler than - properly plumbing the data through. + properly plumbing the data through. Transitive header-to-header includes + are picked up by `resolve_cfile_deps` in `mypyc_build`, which can read + the on-disk headers after every group has written its files. Arguments: - cfiles: A list of (file name, file contents) pairs. + cfiles: A list of (file name, file contents) pairs. Contents must be + non-empty; callers handling cached groups must re-read the .c + from disk before calling, otherwise direct includes are missed + and Extension.depends ends up empty. """ - headers: set[str] = set() + assert all( + contents for _, contents in cfiles + ), "get_header_deps requires non-empty file contents" + headers: set[tuple[bool, str]] = set() for _, contents in cfiles: - headers.update(re.findall(r'#include "(.*)"', contents)) + headers.update(_extract_includes(contents)) return sorted(headers) +def resolve_cfile_deps( + cfile_dir: str, direct_includes: list[tuple[bool, str]], target_dir: str +) -> set[str]: + """ + Resolve a .c file's `#include`s to on-disk paths, walking transitively through resolved headers. + + The C preprocessor resolves `#include "foo"` against the includer's directory first, then via + -I, while `#include ` only uses -I. We mirror that exactly: quoted includes are searched + in (includer_dir, target_dir) order, and angled includes are searched in target_dir only. + `target_dir` is the only -I path that holds files we generate; anything we cannot resolve under + it (or, for quoted form, the includer's dir) is dropped. Other headers like `` and + `` live elsewhere and do not change between builds, so they are not real dependencies + for incremental purposes. + + The walk is transitive: each resolved header is opened and scanned for its own `#include` + directives. Without this, cross-group export-table headers reached via `__native_internal_.h` + (which includes ``) would be missed, and edits that shift struct + offsets in `other_group` would not trigger a recompile of the consumer's .o file. Its baked-in + offsets would then resolve to whatever class/function now occupies that slot => runtime corruption. + + Returns a set of resolved paths suitable for use as an Extension.depends list. + """ + resolved: set[str] = set() + + # Worklist of (search_dir, is_angled, header_name). search_dir is the includer's directory; for the + # initial cfile it is the cfile's dir, for a transitively-included header it is that header's dir. + # It is only consulted for quoted-form includes. + worklist: list[tuple[str, bool, str]] = [ + (cfile_dir, is_angled, dep) for is_angled, dep in direct_includes + ] + + while worklist: + search_dir, is_angled, dep = worklist.pop() + # Quoted form: includer's dir first, then -I (target_dir). + # Angled form: -I only (skips the includer's dir). + search_bases = (target_dir,) if is_angled else (search_dir, target_dir) + for base in search_bases: + candidate = os.path.normpath(os.path.join(base, dep)) + if not os.path.exists(candidate): + continue + if candidate in resolved: + break + resolved.add(candidate) + # Recurse only into headers. Some lib-rt sources are pulled in as `#include "init.c"` etc.; + # those do not resolve under target_dir so they get filtered out before we would try to scan + # them, but the .h guard is a cheap belt-and-braces. + if candidate.endswith(".h"): + try: + with open(candidate, encoding="utf-8") as f: + header_contents = f.read() + except OSError: + header_contents = "" + sub_dir = os.path.dirname(candidate) + for sub_angled, sub in _extract_includes(header_contents): + worklist.append((sub_dir, sub_angled, sub)) + break + return resolved + + def mypyc_build( paths: list[str], compiler_options: CompilerOptions, @@ -565,20 +736,48 @@ def mypyc_build( for (path, dirs, internal) in skip_cgen_input[1] ] - # Write out the generated C and collect the files for each group + # Write out the generated C and collect the files for each group. # Should this be here?? - group_cfilenames: list[tuple[list[str], list[str]]] = [] + # + # Header resolution is deferred to a second pass: a header in one group may include a header + # generated by another group, so resolving here misses cross-group deps for groups processed first. + pending: list[list[tuple[str, list[tuple[bool, str]]]]] = [] for cfiles in group_cfiles: - cfilenames = [] + per_cfile_deps: list[tuple[str, list[tuple[bool, str]]]] = [] for cfile, ctext in cfiles: cfile = os.path.join(compiler_options.target_dir, cfile) - if not options.mypyc_skip_c_generation: + + # Empty contents marks a file the previous run already wrote + # (fully-cached group): skip the rewrite and just reuse it. + if ctext and not options.mypyc_skip_c_generation: write_file(cfile, ctext) - if os.path.splitext(cfile)[1] == ".c": - cfilenames.append(cfile) - deps = [os.path.join(compiler_options.target_dir, dep) for dep in get_header_deps(cfiles)] - group_cfilenames.append((cfilenames, deps)) + # For fully-cached groups ctext is empty; read the on-disk .c so the dep resolver + # can walk its transitive header chain and populate Extension.depends. Otherwise, + # cross-group export-table header changes (e.g. a new class shifting struct offsets) + # won't trigger a recompile of this cached consumer's .o. + if not ctext and os.path.exists(cfile): + try: + with open(cfile, encoding="utf-8") as _f: + ctext = _f.read() + except OSError: + pass + per_cfile_deps.append((cfile, get_header_deps([(cfile, ctext)]))) + pending.append(per_cfile_deps) + + # Second pass: assemble each group's .c filenames and resolve transitive deps now that every group's + # headers are on disk. See resolve_cfile_deps for the rules. + group_cfilenames: list[tuple[list[str], list[str]]] = [] + for per_cfile in pending: + cfilenames = [cf for cf, _ in per_cfile if os.path.splitext(cf)[1] == ".c"] + deps_set: set[str] = set() + for cfile_full, dep_names in per_cfile: + deps_set.update( + resolve_cfile_deps( + os.path.dirname(cfile_full), dep_names, compiler_options.target_dir + ) + ) + group_cfilenames.append((cfilenames, sorted(deps_set))) return groups, group_cfilenames, source_deps @@ -631,6 +830,9 @@ def get_cflags( # Disables C Preprocessor (cpp) warnings # See https://github.com/mypyc/mypyc/issues/956 "-Wno-cpp", + "-Wno-array-bounds", + "-Wno-stringop-overread", + "-Wno-stringop-overflow", ] if log_trace: cflags.append("-DMYPYC_LOG_TRACE") @@ -751,6 +953,9 @@ def mypycify( have no backward compatibility guarantees! """ + # Skip redundant inplace .so copies on every build_ext invocation. + _patch_setuptools_copy_extensions_to_source() + # Figure out our configuration compiler_options = CompilerOptions( strip_asserts=strip_asserts, @@ -865,4 +1070,9 @@ def mypycify( ) ) + # Tag every extension we own so the build_ext patch knows it's + # safe to skip the redundant inplace copy for these specifically. + for ext in extensions: + setattr(ext, _MYPYC_EXTENSION_MARKER, True) + return extensions diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index 45ff34ab045d5..57cce6a3fe8fe 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -2,14 +2,12 @@ from __future__ import annotations -import pprint import sys -import textwrap from collections.abc import Callable from typing import Final from mypyc.codegen.cstring import c_string_initializer -from mypyc.codegen.literals import Literals +from mypyc.codegen.literals import Literals, literal_sort_key from mypyc.common import ( ATTR_PREFIX, BITMAP_BITS, @@ -237,24 +235,16 @@ def attr(self, name: str) -> str: return ATTR_PREFIX + name def object_annotation(self, obj: object, line: str) -> str: - """Build a C comment with an object's string representation. + """Build a C comment with a literal value's string representation. - If the comment exceeds the line length limit, it's wrapped into a - multiline string (with the extra lines indented to be aligned with - the first line's comment). + This is a debugging aid that makes generated C easier to read. - If it contains illegal characters, an empty string is returned.""" - line_width = self._indent + len(line) - formatted = pprint.pformat(obj, compact=True, width=max(90 - line_width, 20)) - if any(x in formatted for x in ("/*", "*/", "\0")): + If it contains illegal characters or is too long, return an empty string. + """ + formatted = stable_literal_repr(obj) + if any(x in formatted for x in ("/*", "*/", "\0")) or len(formatted) >= 256: return "" - - if "\n" in formatted: - first_line, rest = formatted.split("\n", maxsplit=1) - comment_continued = textwrap.indent(rest, (line_width + 3) * " ") - return f" /* {first_line}\n{comment_continued} */" - else: - return f" /* {formatted} */" + return f" /* {formatted} */" def emit_line(self, line: str = "", *, ann: object = None) -> None: if line.startswith("}"): @@ -326,6 +316,18 @@ def get_group_prefix(self, obj: ClassIR | FuncDecl) -> str: # See docs above return self.get_module_group_prefix(obj.module_name) + def register_group_dep(self, cl: ClassIR) -> None: + """Record `cl`'s defining group as a cross-group dep, if any. + + Call this when emitting code that refers to `cl`'s struct + layout: the .c file consuming that layout needs the defining + group's `__native_*.h` included, and group_deps drives which + headers get pulled in. + """ + target_group = self.context.group_map.get(cl.module_name) + if target_group and target_group != self.context.group_name: + self.context.group_deps.add(target_group) + def static_name(self, id: str, module: str | None, prefix: str = STATIC_PREFIX) -> str: """Create name of a C static variable. @@ -1474,3 +1476,21 @@ def native_function_doc_initializer(func: FuncIR) -> str: return "NULL" docstring = f"{text_sig}\n--\n\n" return c_string_initializer(docstring.encode("ascii", errors="backslashreplace")) + + +def stable_literal_repr(obj: object) -> str: + """Return a single-line repr of a literal value. + + Behaves like repr() for most values, but renders frozenset members in a + deterministic order (frozenset iteration order is hash-seed dependent). + """ + if isinstance(obj, frozenset): + if not obj: + return "frozenset()" + items = ", ".join(stable_literal_repr(item) for item in sorted(obj, key=literal_sort_key)) + return "frozenset({" + items + "})" + elif isinstance(obj, tuple): + if len(obj) == 1: + return "(" + stable_literal_repr(obj[0]) + ",)" + return "(" + ", ".join(stable_literal_repr(item) for item in obj) + ")" + return repr(obj) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index a312311b21a04..a1127b15ad9d1 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -29,6 +29,8 @@ BITMAP_BITS, BITMAP_TYPE, CPYFUNCTION_NAME, + IS_FREE_THREADED, + MYPYC_DEFAULTS_SETUP, NATIVE_PREFIX, PREFIX, REG_PREFIX, @@ -42,7 +44,7 @@ FuncIR, get_text_signature, ) -from mypyc.ir.rtypes import RTuple, RType, object_rprimitive +from mypyc.ir.rtypes import RTuple, RType, is_simple_refcounted_pointer, object_rprimitive from mypyc.namegen import NameGenerator from mypyc.sametype import is_same_type @@ -285,7 +287,7 @@ def emit_line() -> None: # If the class has a method to initialize default attribute # values, we need to call it during initialization. - defaults_fn = cl.get_method("__mypyc_defaults_setup") + defaults_fn = cl.get_method(MYPYC_DEFAULTS_SETUP) # If there is a __init__ method, we'll use it in the native constructor. init_fn = cl.get_method("__init__") @@ -683,8 +685,8 @@ def emit_attr_defaults_func_call(defaults_fn: FuncIR, self_name: str, emitter: E The code returns NULL on a raised exception. """ emitter.emit_lines( - "if ({}{}((PyObject *){}) == 0) {{".format( - NATIVE_PREFIX, defaults_fn.cname(emitter.names), self_name + "if ({}((PyObject *){}) == 0) {{".format( + emitter.native_function_call(defaults_fn.decl), self_name ), "Py_DECREF(self);", "return NULL;", @@ -1064,12 +1066,14 @@ def generate_getseter_declarations(cl: ClassIR, emitter: Emitter) -> None: getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names) ) ) - emitter.emit_line("static int") - emitter.emit_line( - "{}({} *self, PyObject *value, void *closure);".format( - setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names) + # Final attributes are read-only, so they have no setter. + if attr not in cl.final_attributes: + emitter.emit_line("static int") + emitter.emit_line( + "{}({} *self, PyObject *value, void *closure);".format( + setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names) + ) ) - ) for prop, (getter, setter) in cl.properties.items(): if getter.decl.implicit: @@ -1098,11 +1102,15 @@ def generate_getseters_table(cl: ClassIR, name: str, emitter: Emitter) -> None: if not cl.is_trait: for attr in cl.attributes: emitter.emit_line(f'{{"{attr}",') - emitter.emit_line( - " (getter){}, (setter){},".format( - getter_name(cl, attr, emitter.names), setter_name(cl, attr, emitter.names) + if attr in cl.final_attributes: + # Final attributes are read-only, so emit a NULL setter. + emitter.emit_line(f" (getter){getter_name(cl, attr, emitter.names)}, NULL,") + else: + emitter.emit_line( + " (getter){}, (setter){},".format( + getter_name(cl, attr, emitter.names), setter_name(cl, attr, emitter.names) + ) ) - ) emitter.emit_line(" NULL, NULL},") for prop, (getter, setter) in cl.properties.items(): if getter.decl.implicit: @@ -1128,8 +1136,10 @@ def generate_getseters(cl: ClassIR, emitter: Emitter) -> None: if not cl.is_trait: for i, (attr, rtype) in enumerate(cl.attributes.items()): generate_getter(cl, attr, rtype, emitter) - emitter.emit_line("") - generate_setter(cl, attr, rtype, emitter) + # Final attributes are read-only, so they have no setter. + if attr not in cl.final_attributes: + emitter.emit_line("") + generate_setter(cl, attr, rtype, emitter) if i < len(cl.attributes) - 1: emitter.emit_line("") for prop, (getter, setter) in cl.properties.items(): @@ -1156,6 +1166,32 @@ def generate_getter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N emitter.emit_line("{") attr_expr = f"self->{attr_field}" + if IS_FREE_THREADED and is_simple_refcounted_pointer(rtype): + # In free-threaded builds, load the attribute and take a new reference + # atomically to avoid a use-after-free race with a concurrent setter. + # CPy_GetAttrRef returns NULL if the attribute is undefined (NULL field), + # which is exactly the error/undefined value for a 'PyObject *' field. + # + # Final attributes are never rebound (no setter), so there is no concurrent + # writer to race with: a plain load + incref is safe. Use the cheaper + # CPy_GetAttrRefFinal, which skips the try-incref and _Py_NewRefWithLock + # slow path entirely (an unconditional Py_INCREF needs no maybe-weakref). + # This getter is generated per defining class, so a direct membership test + # matches the read-only getset table above (no need to walk the MRO). + if attr in cl.final_attributes: + getattr_ref = f"CPy_GetAttrRefFinal((PyObject **)&{attr_expr})" + else: + getattr_ref = f"CPy_GetAttrRef((PyObject **)&{attr_expr})" + emitter.emit_line(f"PyObject *retval = {getattr_ref};") + emitter.emit_line("if (unlikely(retval == NULL)) {") + emitter.emit_line("PyErr_SetString(PyExc_AttributeError,") + emitter.emit_line(f' "attribute {repr(attr)} of {repr(cl.name)} undefined");') + emitter.emit_line("return NULL;") + emitter.emit_line("}") + emitter.emit_line("return retval;") + emitter.emit_line("}") + return + # HACK: Don't consider refcounted values as always defined, since it's possible to # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted # values is benign. @@ -1193,6 +1229,30 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N emitter.emit_line("return -1;") emitter.emit_line("}") + if IS_FREE_THREADED and is_simple_refcounted_pointer(rtype): + # In free-threaded builds, publish the new value atomically via + # CPy_SetAttrRef so a concurrent reader (see CPy_GetAttrRef) never sees a + # torn pointer or a freed old value. CPy_SetAttrRef steals its value and + # reclaims the old one, so we cast/type-check the incoming value, take a + # new reference (the setter only borrows 'value'), then hand it over. + # A NULL value deletes the attribute (reclaims the old value, stores NULL). + if deletable: + emitter.emit_line("if (value != NULL) {") + if is_same_type(rtype, object_rprimitive): + emitter.emit_line("PyObject *tmp = value;") + else: + emitter.emit_cast("value", "tmp", rtype, declare_dest=True) + emitter.emit_lines("if (!tmp)", " return -1;") + emitter.emit_inc_ref("tmp", rtype) + emitter.emit_line(f"CPy_SetAttrRef((PyObject **)&self->{attr_field}, tmp);") + if deletable: + emitter.emit_line("} else {") + emitter.emit_line(f"CPy_SetAttrRef((PyObject **)&self->{attr_field}, NULL);") + emitter.emit_line("}") + emitter.emit_line("return 0;") + emitter.emit_line("}") + return + # HACK: Don't consider refcounted values as always defined, since it's possible to # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted # values is benign. @@ -1210,7 +1270,15 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N emitter.emit_line("if (value != NULL) {") if rtype.is_unboxed: - emitter.emit_unbox("value", "tmp", rtype, error=ReturnHandler("-1"), declare_dest=True) + # Borrow the unboxed value: emit_inc_ref below takes the single owned + # reference, matching the borrowed-then-incref pattern of the other two + # branches. Without borrow=True, emit_unbox already creates a new + # reference for refcounted unboxed types (e.g. CPyTagged boxed ints, + # tuples with refcounted fields), so the emit_inc_ref would double the + # reference and leak the stored value on every set via this setter. + emitter.emit_unbox( + "value", "tmp", rtype, error=ReturnHandler("-1"), declare_dest=True, borrow=True + ) elif is_same_type(rtype, object_rprimitive): emitter.emit_line("PyObject *tmp = value;") else: diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index e4a8922a103d4..4c3c17d047c8a 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -12,7 +12,13 @@ TracebackAndGotoHandler, c_array_initializer, ) -from mypyc.common import GENERATOR_ATTRIBUTE_PREFIX, HAVE_IMMORTAL, NATIVE_PREFIX, REG_PREFIX +from mypyc.common import ( + GENERATOR_ATTRIBUTE_PREFIX, + HAVE_IMMORTAL, + IS_FREE_THREADED, + NATIVE_PREFIX, + REG_PREFIX, +) from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values from mypyc.ir.ops import ( @@ -83,6 +89,7 @@ is_int_rprimitive, is_none_rprimitive, is_pointer_rprimitive, + is_simple_refcounted_pointer, is_tagged, ) @@ -360,6 +367,11 @@ def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> st classes, and *(obj + attr_offset) for attributes defined by traits. We also insert all necessary C casts here. """ + # The struct cast below needs the defining group's __native.h + # included by the consuming .c file. Record both the receiver + # and declaring classes as cross-group deps. + self.emitter.register_group_dep(op.class_type.class_ir) + self.emitter.register_group_dep(decl_cl) cast = f"({op.class_type.struct_name(self.emitter.names)} *)" if decl_cl.is_trait and op.class_type.class_ir.is_trait: # For pure trait access find the offset first, offsets @@ -389,6 +401,35 @@ def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> st cast = f"({decl_cl.struct_name(self.emitter.names)} *)" return f"({cast}{obj})->{self.emitter.attr(op.attr)}" + def emit_load_attr_take_ref( + self, dest: str, obj: str, op: GetAttr, cl: ClassIR, attr_rtype: RType, attr_expr: str + ) -> bool: + """Emit the load of a native attribute into 'dest', taking a new reference. + + On free-threaded builds, reading a single reference-counted 'PyObject *' field + and taking a new reference must be done atomically to avoid a use-after-free + race with a concurrent setter. CPy_GetAttrRef performs the load and incref + atomically and returns a new reference (or NULL if undefined), so callers must + NOT emit a separate inc_ref. Return True in that case so the caller can skip it. + + Final attributes are never rebound (no setter), so there is no concurrent writer + and no use-after-free window; an owned read uses the cheaper CPy_GetAttrRefFinal + (a plain load + incref). Borrowed reads keep the plain load: they are only emitted + for attributes safe to borrow on free-threaded builds (Final and vec attrs -- see + transform_member_expr in irbuild), whose values live as long as their container. + The default (GIL) build always takes the plain-load path and increfs separately. + """ + use_get_attr_ref = ( + IS_FREE_THREADED and is_simple_refcounted_pointer(attr_rtype) and not op.is_borrowed + ) + if use_get_attr_ref and cl.is_final_attr(op.attr): + self.emitter.emit_line(f"{dest} = CPy_GetAttrRefFinal((PyObject **)&{attr_expr});") + elif use_get_attr_ref: + self.emitter.emit_line(f"{dest} = CPy_GetAttrRef((PyObject **)&{attr_expr});") + else: + self.emitter.emit_line(f"{dest} = {attr_expr};") + return use_get_attr_ref + def visit_get_attr(self, op: GetAttr) -> None: if op.allow_error_value: self.get_attr_with_allow_error_value(op) @@ -422,7 +463,9 @@ def visit_get_attr(self, op: GetAttr) -> None: else: # Otherwise, use direct or offset struct access. attr_expr = self.get_attr_expr(obj, op, decl_cl) - self.emitter.emit_line(f"{dest} = {attr_expr};") + use_get_attr_ref = self.emit_load_attr_take_ref( + dest, obj, op, cl, attr_rtype, attr_expr + ) always_defined = cl.is_always_defined(op.attr) merged_branch = None if not always_defined: @@ -453,7 +496,7 @@ def visit_get_attr(self, op: GetAttr) -> None: ) ) - if attr_rtype.is_refcounted and not op.is_borrowed: + if attr_rtype.is_refcounted and not op.is_borrowed and not use_get_attr_ref: if not merged_branch and not always_defined: self.emitter.emit_line("} else {") self.emitter.emit_inc_ref(dest, attr_rtype) @@ -475,16 +518,20 @@ def get_attr_with_allow_error_value(self, op: GetAttr) -> None: cl = rtype.class_ir attr_rtype, decl_cl = cl.attr_details(op.attr) - # Direct struct access without NULL check attr_expr = self.get_attr_expr(obj, op, decl_cl) - self.emitter.emit_line(f"{dest} = {attr_expr};") - - # Only emit inc_ref if not NULL - if attr_rtype.is_refcounted and not op.is_borrowed: - check = self.error_value_check(op, "!=") - self.emitter.emit_line(f"if ({check}) {{") - self.emitter.emit_inc_ref(dest, attr_rtype) - self.emitter.emit_line("}") + # On free-threaded builds this takes a new reference atomically (see + # emit_load_attr_take_ref). CPy_GetAttrRef returns NULL when the field is + # undefined, which is precisely the error value here, so the "NULL without + # AttributeError" behavior is preserved (this op has error_kind ERR_NEVER). + # is_simple_refcounted_pointer excludes tuples/vecs, so error_value_check's + # special cases only matter on the plain-load path (where no ref was taken). + if not self.emit_load_attr_take_ref(dest, obj, op, cl, attr_rtype, attr_expr): + # Only emit inc_ref if not NULL + if attr_rtype.is_refcounted and not op.is_borrowed: + check = self.error_value_check(op, "!=") + self.emitter.emit_line(f"if ({check}) {{") + self.emitter.emit_inc_ref(dest, attr_rtype) + self.emitter.emit_line("}") def next_branch(self) -> Branch | None: if self.op_index + 1 < len(self.ops): @@ -524,6 +571,23 @@ def visit_set_attr(self, op: SetAttr) -> None: op.attr, ) ) + elif IS_FREE_THREADED and is_simple_refcounted_pointer(attr_rtype): + # In free-threaded builds, publishing a single reference-counted + # 'PyObject *' field must be atomic so a concurrent reader (see + # CPy_GetAttrRef) never observes a torn pointer or a freed value. + # Both helpers steal the reference to src. + attr_expr = self.get_attr_expr(obj, op, decl_cl) + if op.is_init: + # The attribute is known to be previously undefined (NULL), so + # there is no old value to reclaim; a relaxed store suffices + # (self's later publication provides the release barrier -- see + # CPy_InitAttrRef). + self.emitter.emit_line(f"CPy_InitAttrRef((PyObject **)&{attr_expr}, {src});") + else: + # Atomically swap in the new value and reclaim the old one. + self.emitter.emit_line(f"CPy_SetAttrRef((PyObject **)&{attr_expr}, {src});") + if op.error_kind == ERR_FALSE: + self.emitter.emit_line(f"{dest} = 1;") else: # ...and struct access for normal attributes. attr_expr = self.get_attr_expr(obj, op, decl_cl) diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index 2025426188412..3b320b5321232 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -56,10 +56,13 @@ shared_lib_name, short_id_from_name, ) +from mypyc.crash import catch_errors from mypyc.errors import Errors from mypyc.ir.deps import ( LIBRT_BASE64, + LIBRT_RANDOM, LIBRT_STRINGS, + LIBRT_THREADING, LIBRT_TIME, LIBRT_VECS, Capsule, @@ -208,15 +211,18 @@ def parse_and_typecheck( ) -> BuildResult: assert options.strict_optional, "strict_optional must be turned on" mypyc_plugin = MypycPlugin(options, compiler_options, groups) - result = build( - sources=sources, - options=options, - alt_lib_path=alt_lib_path, - fscache=fscache, - extra_plugins=[mypyc_plugin], - ) - mypyc_plugin.metastore.close() + try: + result = build( + sources=sources, + options=options, + alt_lib_path=alt_lib_path, + fscache=fscache, + extra_plugins=[mypyc_plugin], + ) + finally: + mypyc_plugin.metastore.close() if result.errors: + result.manager.metastore.close() raise CompileError(result.errors) return result @@ -258,29 +264,31 @@ def compile_scc_to_ir( env_user_functions[cls.env_user_function] = cls for module in modules.values(): + module_path = result.graph[module.fullname].xpath for fn in module.functions: - # Insert checks for uninitialized values. - insert_uninit_checks(fn, compiler_options.strict_traceback_checks) - # Insert exception handling. - insert_exception_handling(fn, compiler_options.strict_traceback_checks) - # Insert reference count handling. - insert_ref_count_opcodes(fn) - - if fn in env_user_functions: - insert_spills(fn, env_user_functions[fn]) - - if compiler_options.log_trace: - insert_event_trace_logging(fn, compiler_options) - - # Switch to lower abstraction level IR. - lower_ir(fn, compiler_options) - # Calculate implicit module dependencies (needed for librt) - deps = find_implicit_op_dependencies(fn) - if deps is not None: - module.dependencies.update(deps) - # Perform optimizations. - do_copy_propagation(fn, compiler_options) - do_flag_elimination(fn, compiler_options) + with catch_errors(module_path, fn.line): + # Insert checks for uninitialized values. + insert_uninit_checks(fn, compiler_options.strict_traceback_checks) + # Insert exception handling. + insert_exception_handling(fn, compiler_options.strict_traceback_checks) + # Insert reference count handling. + insert_ref_count_opcodes(fn) + + if fn in env_user_functions: + insert_spills(fn, env_user_functions[fn]) + + if compiler_options.log_trace: + insert_event_trace_logging(fn, compiler_options) + + # Switch to lower abstraction level IR. + lower_ir(fn, compiler_options) + # Calculate implicit module dependencies (needed for librt) + deps = find_implicit_op_dependencies(fn) + if deps is not None: + module.dependencies.update(deps) + # Perform optimizations. + do_copy_propagation(fn, compiler_options) + do_flag_elimination(fn, compiler_options) # Calculate implicit dependencies from class attribute types for cl in module.classes: @@ -362,7 +370,12 @@ def compile_ir_to_c( if source.module in modules } if not group_modules: - ctext[group_name] = [] + # Fully-cached group (e.g. pip's second setup.py invoke for + # the wheel phase): no fresh IR was produced. Reuse the file + # list recorded in any module's IR cache so the linker still + # sees the previous run's outputs; empty content is a "do + # not rewrite" sentinel for mypyc_build. + ctext[group_name] = _load_cached_group_files(group_sources, result) continue generator = GroupGenerator( group_modules, source_paths, group_name, mapper.group_map, names, compiler_options @@ -372,6 +385,32 @@ def compile_ir_to_c( return ctext +def _load_cached_group_files( + group_sources: list[BuildSource], result: BuildResult +) -> list[tuple[str, str]]: + """Read the .c/.h paths recorded for this group on the previous run. + + All modules in a group share the same src_hashes map, so the first + readable IR cache is sufficient. Returns paths paired with empty + content so callers can distinguish "reuse on disk" from "newly + generated". + """ + for source in group_sources: + state = result.graph.get(source.module) + if state is None: + continue + try: + ir_json = result.manager.metastore.read(get_state_ir_cache_name(state)) + except (FileNotFoundError, OSError): + continue + try: + ir_data = json.loads(ir_json) + except json.JSONDecodeError: + continue + return [(path, "") for path in ir_data.get("src_hashes", {})] + return [] + + def get_ir_cache_name(id: str, path: str, options: Options) -> str: meta_path, _, _ = get_cache_names(id, path, options) # Mypyc uses JSON cache even with --fixed-format-cache (for now). @@ -614,16 +653,19 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: base_emitter = Emitter(self.context) # Optionally just include the runtime library c files to - # reduce the number of compiler invocations needed + # reduce the number of compiler invocations needed. + # Use <> form (only -I paths) so a shim file with the same + # basename as a runtime file can't shadow it. Triggered by + # mypyc/lower/int_ops.py vs lib-rt/int_ops.c on mypy self-compile. if self.compiler_options.include_runtime_files: for name in RUNTIME_C_FILES: - base_emitter.emit_line(f'#include "{name}"') + base_emitter.emit_line(f"#include <{name}>") # Include conditional source files source_deps = collect_source_dependencies(self.modules) for source_dep in sorted(source_deps, key=lambda d: d.path): - base_emitter.emit_line(f'#include "{source_dep.path}"') + base_emitter.emit_line(f"#include <{source_dep.path}>") if self.compiler_options.depends_on_librt_internal: - base_emitter.emit_line('#include "internal/librt_internal_api.c"') + base_emitter.emit_line("#include ") base_emitter.emit_line(f'#include "__native{self.short_group_suffix}.h"') base_emitter.emit_line(f'#include "__native_internal{self.short_group_suffix}.h"') emitter = base_emitter @@ -1220,10 +1262,18 @@ def emit_module_exec_func( emitter.emit_line("if (import_librt_time() < 0) {") emitter.emit_line("return -1;") emitter.emit_line("}") + if LIBRT_THREADING in module.dependencies: + emitter.emit_line("if (import_librt_threading() < 0) {") + emitter.emit_line("return -1;") + emitter.emit_line("}") if LIBRT_VECS in module.dependencies: emitter.emit_line("if (import_librt_vecs() < 0) {") emitter.emit_line("return -1;") emitter.emit_line("}") + if LIBRT_RANDOM in module.dependencies: + emitter.emit_line("if (import_librt_random() < 0) {") + emitter.emit_line("return -1;") + emitter.emit_line("}") emitter.emit_line("PyObject* modname = NULL;") if self.multi_phase_init: emitter.emit_line(f"{module_static} = module;") diff --git a/mypyc/codegen/literals.py b/mypyc/codegen/literals.py index ed1ff93277167..a8d4650b5c294 100644 --- a/mypyc/codegen/literals.py +++ b/mypyc/codegen/literals.py @@ -65,7 +65,8 @@ def record_literal(self, value: LiteralValue) -> None: elif isinstance(value, frozenset): frozenset_literals = self.frozenset_literals if value not in frozenset_literals: - for item in value: + # Sort members so that we don't depend on frozenset iteration order. + for item in sorted(value, key=literal_sort_key): assert _is_literal_value(item) self.record_literal(item) frozenset_literals[value] = len(frozenset_literals) @@ -140,10 +141,14 @@ def encoded_tuple_values(self) -> list[str]: return self._encode_collection_values(self.tuple_literals) def encoded_frozenset_values(self) -> list[str]: - return self._encode_collection_values(self.frozenset_literals) + # Ensure deterministic frozenset item order by sorting items. + return self._encode_collection_values(self.frozenset_literals, sort_items=True) def _encode_collection_values( - self, values: dict[tuple[object, ...], int] | dict[frozenset[object], int] + self, + values: dict[tuple[object, ...], int] | dict[frozenset[object], int], + *, + sort_items: bool = False, ) -> list[str]: """Encode tuple/frozenset values into a C array. @@ -164,7 +169,8 @@ def _encode_collection_values( for i in range(count): value = value_by_index[i] result.append(str(len(value))) - for item in value: + items = sorted(value, key=literal_sort_key) if sort_items else value + for item in items: assert _is_literal_value(item) index = self.literal_index(item) result.append(str(index)) @@ -299,3 +305,13 @@ def _encode_complex_values(values: dict[complex, int]) -> list[str]: result.append(float_to_c(value.real)) result.append(float_to_c(value.imag)) return result + + +def literal_sort_key(value: object) -> tuple[object, ...]: + """Return a sort key for a literal value.""" + if isinstance(value, frozenset): + # Sort items to avoid depending on the unpredictable iteration order. + return ("frozenset", tuple(sorted(literal_sort_key(item) for item in value))) + elif isinstance(value, tuple): + return ("tuple", tuple(literal_sort_key(item) for item in value)) + return (type(value).__name__, repr(value)) diff --git a/mypyc/common.py b/mypyc/common.py index 64fe8126087b8..fa34647c5c729 100644 --- a/mypyc/common.py +++ b/mypyc/common.py @@ -24,6 +24,7 @@ LAMBDA_NAME: Final = "__mypyc_lambda__" PROPSET_PREFIX: Final = "__mypyc_setter__" SELF_NAME: Final = "__mypyc_self__" +MYPYC_DEFAULTS_SETUP: Final = "__mypyc_defaults_setup" GENERATOR_ATTRIBUTE_PREFIX: Final = "__mypyc_generator_attribute__" CPYFUNCTION_NAME = "__cpyfunction__" @@ -67,6 +68,14 @@ BITMAP_TYPE: Final = "uint32_t" BITMAP_BITS: Final = 32 +# Constant for keeping a (often borrowed) op alive for a short time (a few subexpressions, +# no arbitrary computation or memory allocations), until flush_keep_alives() is called. +KEEP_ALIVE_SHORT_LIVED: Final = 0 +# Keep value alive longer, up to until the current top-level expression has been fully +# evaluated. This allows keeping alive values across function calls and arbitrary +# computation. Note that some expressions (e.g. lambdas), restrict the scope of borrowing. +KEEP_ALIVE_WHOLE_EXPRESSION: Final = 1 + # Runtime C library files that are always included (some ops may bring # extra dependencies via mypyc.ir.deps.SourceDep or mypyc.ir.deps.HeaderDep) RUNTIME_C_FILES: Final = [ diff --git a/mypyc/crash.py b/mypyc/crash.py index 1227aa8978af2..498651a23b1e7 100644 --- a/mypyc/crash.py +++ b/mypyc/crash.py @@ -29,4 +29,8 @@ def crash_report(module_path: str, line: int) -> NoReturn: for s in traceback.format_list(tb + tb2): print(s.rstrip("\n")) print(f"{module_path}:{line}: {type(err).__name__}: {err}") + print( + f"{module_path}:{line}: note: this is an internal mypyc error; " + "please report a bug at https://github.com/mypyc/mypyc/issues" + ) raise SystemExit(2) diff --git a/mypyc/doc/differences_from_python.rst b/mypyc/doc/differences_from_python.rst index b910e3b3c9290..7e6cf37154d3e 100644 --- a/mypyc/doc/differences_from_python.rst +++ b/mypyc/doc/differences_from_python.rst @@ -237,28 +237,87 @@ nested function calls, typically due to out-of-control recursion. Final values ------------ -Compiled code replaces a reference to an attribute declared ``Final`` with -the value of the attribute computed at compile time. This is an example of -:ref:`early binding `. Example:: +Mypy treats variables and attributes defined as ``Final`` specially. +For instance attributes of native classes, mypyc prevents reassignment +of these attributes. + +Mypyc replaces references to a variable or attribute declared ``Final`` +with the value of the attribute computed at compile time, when it can +be determined during compilation. Example:: MAX: Final = 100 def limit_to_max(x: int) -> int: - if x > MAX: - return MAX - return x + if x > MAX: + return MAX + return x The two references to ``MAX`` don't involve any module namespace lookups, and are equivalent to this code:: def limit_to_max(x: int) -> int: - if x > 100: - return 100 - return x + if x > 100: + return 100 + return x When run as interpreted, the first example will execute slower due to -the extra namespace lookups. In interpreted code final attributes can -also be modified. +the extra namespace lookups. + +For a final class attribute or global variable whose value can't be +determined during compilation, mypyc defines a hidden native variable +that stores the value when the initialization assignment is evaluated. +Redefining the attribute using ``setattr`` or direct namespace access +has no effect for mypyc-compiled code (in the same compilation unit). + +Final global variables and class attributes are faster to read in +compiled code, since they don't have to be looked up from a namespace +dictionary. Thus using a ``Final`` object that holds mutable state in an +attribute tends to be faster than a plain global variable in compiled code:: + + class Counter: + def __init__(self) -> None: + self.value = 0 + + x: Final = Counter() + y = 0 + + def inc() -> None: + # Faster: no Python namespace lookup + x.value += 1 + # Slower: access through globals() namespace dictionary + global y + y += 1 + +.. _free-threading: + +Free threading +-------------- + +Mypyc supports free threading. However, optimized primitive operations in +compiled code may have different atomicity properties compared to CPython. +Use explicit synchronization if code depends on operations being atomic and +race conditions are possible. This is already the recommended approach for +normal Python code. You can often use :ref:`librt.threading.Lock ` +(or :py:class:`threading.Lock`, which is less efficient than +``librt.threading.Lock``) to fix data races. + +Since mypyc 2.3, the vast majority of operations are memory safe even if +there are race conditions (unlike earlier mypyc releases). This includes +list operations and access to native instance attributes (except for +a few less common use cases that will be fixed in future releases). + +.. note:: + + Operations that aren't safe under race conditions in interpreted CPython + are not expected to be memory safe in compiled code either. + Some :ref:`librt ` features are heavily optimized for performance and + don't guarantee memory safety when there are race conditions + (notably the :ref:`vec ` type). + +It's always safe to perform read-only operations concurrently. Using +objects with final attributes and tuple objects can help prevent +race conditions without introducing extra overhead from explicit +synchronization operations. Unsupported features -------------------- @@ -284,10 +343,8 @@ Dunder methods Native classes **cannot** use these dunders. If defined, they will not work as expected. -* ``__del__`` * ``__index__`` -* ``__getattr__``, ``__getattribute__`` -* ``__setattr__`` +* ``__getattribute__`` * ``__delattr__`` Generator expressions @@ -318,7 +375,6 @@ non-exhaustive list of what won't work: - Compiled methods aren't considered methods by ``inspect.ismethod`` - ``inspect.signature`` chokes on compiled functions with default arguments that are not simple literals -- ``inspect.iscoroutinefunction`` and ``asyncio.iscoroutinefunction`` will always return False for compiled functions, even those defined with `async def` Profiling hooks and tracing *************************** diff --git a/mypyc/doc/index.rst b/mypyc/doc/index.rst index 8be1086bdd707..3f74595dd47fb 100644 --- a/mypyc/doc/index.rst +++ b/mypyc/doc/index.rst @@ -33,7 +33,9 @@ generate fast code. librt librt_base64 + librt_random librt_strings + librt_threading librt_time librt_vecs diff --git a/mypyc/doc/introduction.rst b/mypyc/doc/introduction.rst index 785316d959050..c14ef53ed1473 100644 --- a/mypyc/doc/introduction.rst +++ b/mypyc/doc/introduction.rst @@ -23,8 +23,8 @@ also as normal, interpreted Python modules. Existing code with type annotations is often **1.5x to 5x** faster when compiled. Code tuned for mypyc can be **5x to 10x** faster. -Mypyc currently aims to speed up non-numeric code, such as server -applications. Mypyc is also used to compile itself (and mypy). +Mypyc aims to be effective in many common use cases, including +server applications. Mypyc is also used to compile itself (and mypy). Why mypyc? ---------- @@ -54,10 +54,11 @@ requires only minor changes to compile using mypyc. normal Python code. You can use interpreted Python during development, with familiar and fast workflows. -**Runtime type safety.** Mypyc protects you from segfaults and memory -corruption. Any unexpected runtime type safety violation is a bug in -mypyc. Runtime values are checked against type annotations. (Without -mypyc, type annotations are ignored at runtime.) +**Runtime type safety.** Mypyc partially protects you from segfaults and +memory corruption (see :ref:`free-threading` for limitations). Any +unexpected runtime type safety violation is a bug in mypyc. Runtime +values are checked against type annotations. (Without mypyc, type +annotations are ignored at runtime.) **Find errors statically.** Mypyc uses mypy for static type checking that helps catch many bugs. @@ -110,8 +111,7 @@ differently, however: * Mypyc performs strict enforcement of type annotations at runtime, resulting in better runtime type safety and easier debugging. -Unlike Cython, mypyc doesn't directly support interfacing with C libraries -or speeding up numeric code. +Unlike Cython, mypyc doesn't directly support interfacing with C libraries. How does it work ---------------- diff --git a/mypyc/doc/librt.rst b/mypyc/doc/librt.rst index a9492e1d61268..681efe47e7603 100644 --- a/mypyc/doc/librt.rst +++ b/mypyc/doc/librt.rst @@ -26,8 +26,12 @@ Follow submodule links in the table to a detailed description of each submodule. - Description * - :doc:`librt.base64 ` - Fast Base64 encoding and decoding + * - :doc:`librt.random ` + - Pseudorandom number generation * - :doc:`librt.strings ` - String and bytes utilities + * - :doc:`librt.threading ` + - Threading primitives * - :doc:`librt.time ` - Time utilities * - :doc:`librt.vecs ` diff --git a/mypyc/doc/librt_random.rst b/mypyc/doc/librt_random.rst new file mode 100644 index 0000000000000..d5543661ce987 --- /dev/null +++ b/mypyc/doc/librt_random.rst @@ -0,0 +1,97 @@ +.. _librt-random: + +librt.random +============ + +The ``librt.random`` module is part of the ``librt`` package on PyPI, and it provides +pseudorandom number generation utilities. It can be used as a significantly faster +alternative to the stdlib :mod:`random` module in compiled code. It can also be faster +than stdlib ``random`` in interpreted code, depending on use case. + +The module uses the `ChaCha8 `__ algorithm with forward +secrecy. It is **not** suitable for cryptographic use, but it provides high-quality, +statistically uniform output. + +Functions +--------- + +The module provides module-level functions that use thread-local state, so they are +safe to call concurrently from multiple threads without external locking, and they +scale well even if used from multiple threads: + +.. function:: random() -> float + + Return a random floating-point number in the range [0.0, 1.0). + +.. function:: randint(a: i64, b: i64) -> i64 + + Return a random integer *n* such that *a* <= *n* <= *b*. + +.. function:: randrange(stop: i64, /) -> i64 + randrange(start: i64, stop: i64, /) -> i64 + + Return a random integer from the range. With one argument, the range is [0, *stop*). + With two arguments, the range is [*start*, *stop*). + +.. function:: seed(n: i64, /) -> None + + Seed the thread-local random number generator. This only affects module-level + functions called from the current thread. + +Random class +------------ + +.. class:: Random(seed: i64 | None = None) + + A pseudorandom number generator instance with its own independent state. Use this + when you need reproducible sequences or want to avoid interference with the + thread-local state used by the module-level functions. + + If *seed* is ``None``, the generator is seeded from OS entropy + (via :func:`os.urandom`). + + It's not safe to use the same ``Random`` instance concurrently from multiple + threads without synchronization on free-threaded Python builds. + + .. method:: random() -> float + + Return a random floating-point number in the range [0.0, 1.0). + + .. method:: randint(a: i64, b: i64) -> i64 + + Return a random integer *n* such that *a* <= *n* <= *b*. + + .. method:: randrange(stop: i64, /) -> i64 + randrange(start: i64, stop: i64, /) -> i64 + + Return a random integer from the range. With one argument, the range is [0, *stop*). + With two arguments, the range is [*start*, *stop*). + + .. method:: seed(n: i64, /) -> None + + Reseed the generator. + +Example +------- + +Using module-level functions:: + + from librt.random import randint, seed + + def roll_dice() -> i64: + return randint(1, 6) + +Using a ``Random`` instance for reproducible sequences:: + + from librt.random import Random + + def generate_data() -> list[i64]: + rng = Random(42) + return [rng.randint(0, 100) for _ in range(10)] + +Backward compatibility +---------------------- + +New versions of this module are not guaranteed to generate the same results when +using the same seed. A specific seed only produces predictable random numbers on a +specific version of ``librt``. In the future we might provide stronger guarantees. diff --git a/mypyc/doc/librt_strings.rst b/mypyc/doc/librt_strings.rst index aece015f4f752..6bb4beef6f25e 100644 --- a/mypyc/doc/librt_strings.rst +++ b/mypyc/doc/librt_strings.rst @@ -15,7 +15,7 @@ Thread safety ``BytesWriter`` and ``StringWriter`` objects are unsafe to access from another thread if they are concurrently modified (on free-threaded Python builds). They are optimized for maximal performance, and they aren't fully synchronized. Read-only access from multiple -threads is safe. +threads is safe, as always. BytesWriter ^^^^^^^^^^^ @@ -101,6 +101,9 @@ StringWriter Functions --------- +Reading and writing binary data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + The ``write_*`` and ``read_*`` functions allow interpreting bytes as packed binary data. They can be used as (much) more efficient but lower-level alternatives to the stdlib :mod:`struct` module in compiled code. @@ -210,3 +213,60 @@ This example writes two binary values and reads them afterwards:: Read a 64-bit floating-point value starting at the given index as a big-endian binary value (8 bytes). + +Code point classification +^^^^^^^^^^^^^^^^^^^^^^^^^ + +These functions classify a single Unicode code point, passed as an ``i32`` integer. They are +faster alternatives to calling the corresponding :py:class:`str` methods on a one-character +string in compiled code. A code point is often obtained via ``ord(s[i])``, which is a +fast operation in compiled code when ``s`` has type :py:class:`str`. + +Each function agrees with the matching :py:class:`str` method applied to the one-character +string ``chr(c)``. Out-of-range inputs (negative values, or values past the maximum Unicode +code point ``0x10FFFF``) return ``False``. + +.. function:: isspace(c: i32, /) -> bool + + Return whether the code point is whitespace. Equivalent to ``chr(c).isspace()``. + +.. function:: isalpha(c: i32, /) -> bool + + Return whether the code point is alphabetic. Equivalent to ``chr(c).isalpha()``. + +.. function:: isdigit(c: i32, /) -> bool + + Return whether the code point is a digit. Equivalent to ``chr(c).isdigit()``. + +.. function:: isalnum(c: i32, /) -> bool + + Return whether the code point is alphanumeric. Equivalent to ``chr(c).isalnum()``. + +.. function:: isidentifier(c: i32, /) -> bool + + Return whether the code point is valid as the first character of a Python identifier. + Equivalent to ``chr(c).isidentifier()``. + +Code point case conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These functions convert the case of a single Unicode code point, passed as an ``i32`` integer, +and return the converted code point as an ``i32``. They are faster alternatives to +:py:meth:`str.upper` / :py:meth:`str.lower` on a one-character string in compiled code. + +For the rare code points whose Unicode uppercase or lowercase form has multiple code points +(e.g. U+00DF ``ß`` has the upper case form ``"SS"``, and U+FB01 ``fi`` maps to ``"FI"``), the +input is returned unchanged, so the signature can stay ``i32 -> i32``. Use :py:meth:`str.upper` +/ :py:meth:`str.lower` when full Unicode case conversion matters, or implement the logic to +handle the special cases explicitly. Out-of-range inputs (negative values, or values past the +maximum Unicode code point ``0x10FFFF``) are returned unchanged. + +.. function:: toupper(c: i32, /) -> i32 + + Return the uppercase of the code point, or the input unchanged if the uppercase does not + consist of exactly one code point. + +.. function:: tolower(c: i32, /) -> i32 + + Return the lowercase of the code point, or the input unchanged if the lowercase does not + consist of exactly one code point. diff --git a/mypyc/doc/librt_threading.rst b/mypyc/doc/librt_threading.rst new file mode 100644 index 0000000000000..0af795bc0daab --- /dev/null +++ b/mypyc/doc/librt_threading.rst @@ -0,0 +1,56 @@ +.. _librt-threading: + +librt.threading +=============== + +The ``librt.threading`` module is part of the ``librt`` package on PyPI, and it includes +threading primitives. + +Classes +------- + +.. _librt-threading-lock: + +Lock +^^^^ + +.. class:: Lock + + A fast mutual exclusion lock. This can be used as a faster replacement for + :py:class:`threading.Lock` in compiled code. + + Like :py:class:`threading.Lock`, a ``Lock`` is *unowned*: it may be released by a thread + other than the one that acquired it, and it doesn't support reentrant (recursive) locking. + A newly created lock is unlocked. + + ``Lock`` can be used as a context manager. The lock is acquired (blocking) on entry and + released on exit, including when the body raises an exception:: + + def example(lock: Lock) -> None: + with lock: + ... # Critical section; the lock is held here. + + ``Lock`` cannot be subclassed. ``Lock`` cannot be used with :py:class:`threading.Condition`. + + .. method:: acquire(blocking: bool = True) -> bool + + Acquire the lock. + + When *blocking* is true (the default), block (if needed) until the lock is available, + acquire it, and return ``True``. When *blocking* is false, acquire the lock only if it + can be done without blocking: return ``True`` if the lock could be acquired, or + ``False`` otherwise (it was already locked by some thread). + + Unlike :py:meth:`threading.Lock.acquire`, there is no *timeout* argument. + + .. method:: release() -> None + + Release the lock, allowing another thread (if any) that is blocked on :meth:`acquire` + to proceed. Since the lock is unowned, it may be released from a thread other than the + one that acquired it. + + Raise :py:exc:`RuntimeError` if the lock is not currently held. + + .. method:: locked() -> bool + + Return ``True`` if the lock is currently held (by any thread). diff --git a/mypyc/doc/librt_vecs.rst b/mypyc/doc/librt_vecs.rst index dad3be621ff4c..a7f5bc3ffbd96 100644 --- a/mypyc/doc/librt_vecs.rst +++ b/mypyc/doc/librt_vecs.rst @@ -1,3 +1,5 @@ +.. _librt-vecs: + librt.vecs ========== @@ -198,15 +200,21 @@ with no unnecessary temporary objects. Thread safety ------------- +The ``vec`` type is heavily optimized for performance, and this means that ``vec`` +gives fewer thread safety guarantees than built-in types such as ``list``. +``vec`` is a lower-level type where implicit synchronization would have a very +significant performance cost. However, since vec lengths are immutable, some race +conditions that lists can be susceptible to are not possible with vecs. + In free-threaded Python builds, it's unsafe to write or modify an item if other threads might be concurrently accessing *the same item*. For example, writing ``v[4]`` is not safe to do if another thread might be reading ``v[4]``. Similarly, two threads concurrently calling ``append`` or ``remove`` on the same vec object is not safe. -This is different from list objects, since vec is a lower-level type where implicit -synchronization would have a significant performance cost. However, since vec lengths -are immutable, some race conditions that lists can be susceptible to are not possible -with vecs. +Similarly, assigning to an instance attribute of a native class with a ``vec`` type +is unsafe if another thread might be accessing the same attribute concurrently +(in a free-threaded Python build only). Using ``Final`` attributes is a way +to prevent this race condition, since ``Final`` attributes cannot be rebound. Implementation details ---------------------- diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index f754275480edc..6ea1eb072999d 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -145,6 +145,8 @@ def __init__( ) # Attributes defined in the class (not inherited) self.attributes: dict[str, RType] = {} + # Final attributes defined in the class (not inherited) + self.final_attributes: set[str] = set() # Deletable attributes self.deletable: list[str] = [] # We populate method_types with the signatures of every method before @@ -267,6 +269,22 @@ def attr_details(self, name: str) -> tuple[RType, ClassIR]: def attr_type(self, name: str) -> RType: return self.attr_details(name)[0] + def is_final_attr(self, name: str) -> bool: + """Is the (possibly inherited) attribute Final, i.e. never rebound? + + A Final attribute is read-only at runtime (it has no setter) and is assigned + exactly once during construction, so it can never be reassigned afterwards. + This makes it safe to borrow on free-threaded builds (no concurrent store can + invalidate a borrowed reference) and lets reads skip the concurrent-writer + guard. Returns False for properties and for attributes this class doesn't have. + """ + for ir in self.mro: + if name in ir.attributes: + return name in ir.final_attributes + if name in ir.property_types: + return False + return False + def method_decl(self, name: str) -> FuncDecl: for ir in self.mro: if name in ir.method_decls: @@ -396,6 +414,7 @@ def serialize(self) -> JsonDict: "ctor": self.ctor.serialize(), # We serialize dicts as lists to ensure order is preserved "attributes": [(k, t.serialize()) for k, t in self.attributes.items()], + "final_attributes": sorted(self.final_attributes), # We try to serialize a name reference, but if the decl isn't in methods # then we can't be sure that will work so we serialize the whole decl. "method_decls": [ @@ -456,6 +475,7 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> ClassIR: ir.builtin_base = data["builtin_base"] ir.ctor = FuncDecl.deserialize(data["ctor"], ctx) ir.attributes = {k: deserialize_type(t, ctx) for k, t in data["attributes"]} + ir.final_attributes = set(data["final_attributes"]) ir.method_decls = { k: ctx.functions[v].decl if isinstance(v, str) else FuncDecl.deserialize(v, ctx) for k, v in data["method_decls"] diff --git a/mypyc/ir/deps.py b/mypyc/ir/deps.py index 20b1f102ee383..b1a27f85fcfe6 100644 --- a/mypyc/ir/deps.py +++ b/mypyc/ir/deps.py @@ -109,6 +109,8 @@ def get_header(self) -> str: LIBRT_BASE64: Final = Capsule("librt.base64") LIBRT_VECS: Final = Capsule("librt.vecs") LIBRT_TIME: Final = Capsule("librt.time") +LIBRT_RANDOM: Final = Capsule("librt.random") +LIBRT_THREADING: Final = Capsule("librt.threading") BYTES_EXTRA_OPS: Final = SourceDep("bytes_extra_ops.c") BYTES_WRITER_EXTRA_OPS: Final = SourceDep("byteswriter_extra_ops.c") diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 485aa84886b32..14e12559be1eb 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -31,7 +31,7 @@ class to enable the new behavior. Sometimes adding a new abstract from mypy_extensions import trait -from mypyc.common import PROPSET_PREFIX +from mypyc.common import KEEP_ALIVE_SHORT_LIVED, PROPSET_PREFIX from mypyc.ir.deps import Dependency from mypyc.ir.rtypes import ( RArray, @@ -39,6 +39,7 @@ class to enable the new behavior. Sometimes adding a new abstract RStruct, RTuple, RType, + RTypeVar, RUnion, RVec, RVoid, @@ -703,7 +704,7 @@ def __init__( self, name: str, arg_types: list[RType], - return_type: RType, # TODO: What about generic? + return_type: RType, var_arg_type: RType | None, truncated_type: RType | None, c_function_name: str | None, @@ -716,6 +717,7 @@ def __init__( is_pure: bool, experimental: bool, dependencies: list[Dependency] | None, + type_params: list[RTypeVar] | None, ) -> None: # Each primitive much have a distinct name, but otherwise they are arbitrary. self.name: Final = name @@ -749,6 +751,7 @@ def __init__( # If this flag is set, the primitive has native integer types and must # be matched using more complex rules. self.is_ambiguous = any(has_fixed_width_int(t) for t in arg_types) + self.type_params = None if not type_params else type_params def __repr__(self) -> str: return f"" @@ -776,11 +779,23 @@ class PrimitiveOp(RegisterOp): code paths for short and long representations. """ - def __init__(self, args: list[Value], desc: PrimitiveDescription, line: int = -1) -> None: + def __init__( + self, + args: list[Value], + desc: PrimitiveDescription, + line: int = -1, + *, + arg_types: list[RType] | None = None, + return_type: RType | None = None, + type_args: list[RType] | None = None, + ) -> None: self.error_kind = desc.error_kind super().__init__(line) self.args = args - self.type = desc.return_type + self.arg_types = arg_types if arg_types is not None else desc.arg_types + self.type = return_type if return_type is not None else desc.return_type + self.is_borrowed = desc.is_borrowed + self.type_args = type_args self.desc = desc def sources(self) -> list[Value]: @@ -883,6 +898,7 @@ def __init__( *, borrow: bool = False, allow_error_value: bool = False, + borrow_scope: int = KEEP_ALIVE_SHORT_LIVED, ) -> None: super().__init__(line) self.obj = obj @@ -897,6 +913,8 @@ def __init__( elif attr_type.error_overlap: self.error_kind = ERR_MAGIC_OVERLAPPING self.is_borrowed = borrow and attr_type.is_refcounted + # How long a borrowed result of this op stays valid (a KEEP_ALIVE_* constant). + self.borrow_scope = borrow_scope def sources(self) -> list[Value]: return [self.obj] diff --git a/mypyc/ir/pprint.py b/mypyc/ir/pprint.py index d0db9f2460a1d..734426ca42de9 100644 --- a/mypyc/ir/pprint.py +++ b/mypyc/ir/pprint.py @@ -231,10 +231,15 @@ def visit_call_c(self, op: CallC) -> str: def visit_primitive_op(self, op: PrimitiveOp) -> str: args_str = ", ".join(self.format("%r", arg) for arg in op.args) + if op.type_args: + joined = ", ".join(str(arg) for arg in op.type_args) + type_args = f"[{joined}]" + else: + type_args = "" if op.is_void: - return self.format("%s %s", op.desc.name, args_str) + return self.format("%s%s %s", op.desc.name, type_args, args_str) else: - return self.format("%r = %s %s", op, op.desc.name, args_str) + return self.format("%r = %s%s %s", op, op.desc.name, type_args, args_str) def visit_truncate(self, op: Truncate) -> str: return self.format("%r = truncate %r: %t to %t", op, op.src, op.src_type, op.type) diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 60e9b49582bc3..afe3d3e9df39b 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -41,7 +41,7 @@ class to enable the new behavior. In rare cases, adding a new from typing import TYPE_CHECKING, ClassVar, Final, Generic, TypeGuard, TypeVar, Union, final from mypyc.common import HAVE_IMMORTAL, IS_32_BIT_PLATFORM, PLATFORM_SIZE, JsonDict, short_name -from mypyc.ir.deps import LIBRT_STRINGS, LIBRT_VECS, Dependency +from mypyc.ir.deps import LIBRT_RANDOM, LIBRT_STRINGS, LIBRT_THREADING, LIBRT_VECS, Dependency from mypyc.namegen import NameGenerator if TYPE_CHECKING: @@ -152,6 +152,9 @@ def visit_rprimitive(self, typ: RPrimitive, /) -> T: def visit_rinstance(self, typ: RInstance, /) -> T: raise NotImplementedError + def visit_rtypevar(self, typ: RTypeVar, /) -> T: + raise RuntimeError("RTypeVar should not be encountered here") + @abstractmethod def visit_rvec(self, typ: RVec, /) -> T: raise NotImplementedError @@ -544,16 +547,41 @@ def __hash__(self) -> int: ("librt.strings.BytesWriter", (LIBRT_STRINGS,)), ("librt.strings.StringWriter", (LIBRT_STRINGS,)), ] +} | { + "librt.random.Random": RPrimitive( + "librt.random.Random", is_unboxed=False, is_refcounted=True, dependencies=(LIBRT_RANDOM,) + ), + "librt.threading.Lock": RPrimitive( + "librt.threading.Lock", + is_unboxed=False, + is_refcounted=True, + dependencies=(LIBRT_THREADING,), + ), } bytes_writer_rprimitive: Final = KNOWN_NATIVE_TYPES["librt.strings.BytesWriter"] string_writer_rprimitive: Final = KNOWN_NATIVE_TYPES["librt.strings.StringWriter"] +random_rprimitive: Final = KNOWN_NATIVE_TYPES["librt.random.Random"] +lock_rprimitive: Final = KNOWN_NATIVE_TYPES["librt.threading.Lock"] def is_native_rprimitive(rtype: RType) -> bool: return isinstance(rtype, RPrimitive) and rtype.name in KNOWN_NATIVE_TYPES +def is_simple_refcounted_pointer(rtype: RType) -> bool: + """Is rtype represented at runtime as a single, reference-counted 'PyObject *'? + + This covers 'object', 'str', containers, instances of native classes and + optional/union types -- everything whose C representation is exactly one + 'PyObject *' field that owns a reference. It excludes unboxed types (tagged + 'int', fixed-width ints, floats, bools), inline tuples ('RTuple'), vectors + ('RVec') and C structs ('RStruct'), which need different treatment for + free-threaded memory safety. + """ + return rtype.is_refcounted and not rtype.is_unboxed and not isinstance(rtype, RStruct) + + def is_tagged(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int_rprimitive or rtype is short_int_rprimitive @@ -742,6 +770,12 @@ def visit_rarray(self, t: RArray) -> str: def visit_rvoid(self, t: RVoid) -> str: assert False, "rvoid in tuple?" + def visit_rtypevar(self, typ: RTypeVar) -> str: + # We need to return something to support generic RTuples, etc. Make sure + # the return value is invalid C so that generic RTuples must be expanded + # before they can be used in IR. + return f"!RTypeVar {typ.id} invalid!" + @final class RTuple(RType): @@ -1008,6 +1042,50 @@ def serialize(self) -> str: return self.name +@final +class RTypeVar(RType): + """Type variable type used for generic primitive ops. + + This allows having generic primitive operations like vec get item, which is + parametrized by the vec item type. + + These types are not valid in any other context outside PrimitiveDescription, + and they will always be substituted during the construction of a PrimitiveOp. + + NOTE: This is not related to mypy's TypeVarType! + """ + + def __init__(self, id: int) -> None: + self.id = id + + @property + def may_be_immortal(self) -> bool: + # RTypeVar must always be substituted before use, so this should never matter. + return False + + def accept(self, visitor: RTypeVisitor[T]) -> T: + return visitor.visit_rtypevar(self) + + def __str__(self) -> str: + return f"" + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> TypeGuard[RTypeVar]: + return isinstance(other, RTypeVar) and other.id == self.id + + def __hash__(self) -> int: + return self.id ^ 12345 + + def serialize(self) -> JsonDict: + return {".class": "RTypeVar", "id": self.id} + + @classmethod + def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> RTypeVar: + return RTypeVar(data["id"]) + + @final class RVec(RType): """librt.vecs.vec[T]""" diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 066954e920165..0b598a00889f5 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -20,12 +20,17 @@ TYPE_VAR_KIND, TYPE_VAR_TUPLE_KIND, ArgKind, + AssignmentExpr, + AwaitExpr, CallExpr, Decorator, + DictionaryComprehension, Expression, FuncDef, + GeneratorExpr, IndexExpr, IntExpr, + LambdaExpr, Lvalue, MemberExpr, MypyFile, @@ -41,7 +46,10 @@ TypeInfo, TypeParam, Var, + YieldExpr, + YieldFromExpr, ) +from mypy.traverser import TraverserVisitor from mypy.types import ( AnyType, DeletedType, @@ -62,6 +70,9 @@ BITMAP_BITS, EXT_SUFFIX, GENERATOR_ATTRIBUTE_PREFIX, + IS_FREE_THREADED, + KEEP_ALIVE_SHORT_LIVED, + KEEP_ALIVE_WHOLE_EXPRESSION, MODULE_PREFIX, SELF_NAME, TEMP_ATTR_NAME, @@ -79,6 +90,7 @@ BasicBlock, Branch, Call, + Cast, ComparisonOp, GetAttr, InitStatic, @@ -146,7 +158,12 @@ from mypyc.options import CompilerOptions from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op, next_op, py_setattr_op -from mypyc.primitives.list_ops import list_get_item_unsafe_op, list_pop_last, to_list +from mypyc.primitives.list_ops import ( + list_get_item_int64_op, + list_get_item_unsafe_op, + list_pop_last, + to_list, +) from mypyc.primitives.misc_ops import ( check_unpack_count_op, get_module_dict_op, @@ -251,11 +268,11 @@ def __init__( self.visitor = visitor - # Class body context: tracks ClassVar names defined so far when processing + # Class body context: tracks ClassVars defined so far when processing # a class body, so that intra-class references (e.g. C = A | B where A is # a ClassVar defined earlier in the same class) can be resolved correctly. # Without this, mypyc looks up such names in module globals, which fails. - self.class_body_classvars: dict[str, None] = {} + self.class_body_classvars: dict[Var, None] = {} self.class_body_obj: Value | None = None self.class_body_ir: ClassIR | None = None @@ -278,6 +295,20 @@ def __init__( self.imports: dict[str, None] = {} self.can_borrow = False + self.expression_depth = 0 + # Symbols (local vars) reassigned via a walrus expression within the current + # top-level expression. Used to avoid borrowing an attribute over the whole + # expression when the borrow root could be rebound (and thus freed) partway. + self.reassigned_in_expr: set[SymbolNode] = set() + # Whether the current top-level expression contains a suspension point + # (await, yield or yield from). A whole-expression borrow can't span such a + # point, since the borrowed value (and its root) live in registers that are + # not spilled into the generator environment across the suspend. + self.expr_has_suspend = False + # Saved expression state for enclosing functions (see enter()/leave()). + self.expression_depth_stack: list[int] = [] + self.reassigned_in_expr_stack: list[set[SymbolNode]] = [] + self.expr_has_suspend_stack: list[bool] = [] # When set, load_globals_dict uses this module instead of self.module_name. # Used by generate_attr_defaults_init for cross-module inherited defaults. @@ -309,6 +340,10 @@ def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> V """ with self.catch_errors(node.line): if isinstance(node, Expression): + self.expression_depth += 1 + if self.expression_depth == 1: + self.reassigned_in_expr = find_walrus_targets(node) + self.expr_has_suspend = expr_has_suspend(node) old_can_borrow = self.can_borrow self.can_borrow = can_borrow try: @@ -323,6 +358,11 @@ def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> V self.can_borrow = old_can_borrow if not can_borrow: self.flush_keep_alives(node.line) + self.expression_depth -= 1 + if self.expression_depth == 0: + self.flush_keep_alives(node.line, scope=KEEP_ALIVE_WHOLE_EXPRESSION) + self.reassigned_in_expr = set() + self.expr_has_suspend = False return res else: try: @@ -331,8 +371,8 @@ def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> V pass return None - def flush_keep_alives(self, line: int) -> None: - self.builder.flush_keep_alives(line) + def flush_keep_alives(self, line: int, *, scope: int = KEEP_ALIVE_SHORT_LIVED) -> None: + self.builder.flush_keep_alives(line, scope=scope) # Pass through methods for the most common low-level builder ops, for convenience. @@ -893,7 +933,10 @@ def process_sequence_assignment( index: Value if is_list_rprimitive(rvalue.type): index = Integer(i, c_pyssize_t_rprimitive) - item_value = self.primitive_op(list_get_item_unsafe_op, [rvalue, index], line) + if not IS_FREE_THREADED: + item_value = self.primitive_op(list_get_item_unsafe_op, [rvalue, index], line) + else: + item_value = self.primitive_op(list_get_item_int64_op, [rvalue, index], line) elif is_tuple_rprimitive(rvalue.type): index = Integer(i, c_pyssize_t_rprimitive) item_value = self.call_c(tuple_get_item_unsafe_op, [rvalue, index], line) @@ -1183,7 +1226,9 @@ def is_synthetic_type(self, typ: TypeInfo) -> bool: return typ.is_named_tuple or typ.is_newtype or typ.typeddict_type is not None def get_final_ref(self, expr: MemberExpr) -> tuple[str, Var, bool] | None: - """Check if `expr` is a final attribute. + """Check if `expr` is a final class or module attribute. + + Return False for instance attributes. This needs to be done differently for class and module attributes to correctly determine fully qualified name. Return a tuple that consists of @@ -1332,6 +1377,15 @@ def enter(self, fn_info: FuncInfo | str = "", *, ret_type: RType = none_rprimiti self.fn_info = fn_info self.fn_infos.append(self.fn_info) self.ret_types.append(ret_type) + # A function body is its own top-level expression context, even when the + # function (e.g. a lambda) is being generated in the middle of an outer + # expression. Save the outer expression state and start fresh. + self.expression_depth_stack.append(self.expression_depth) + self.reassigned_in_expr_stack.append(self.reassigned_in_expr) + self.expr_has_suspend_stack.append(self.expr_has_suspend) + self.expression_depth = 0 + self.reassigned_in_expr = set() + self.expr_has_suspend = False if fn_info.is_generator: self.nonlocal_control.append(GeneratorNonlocalControl()) else: @@ -1345,6 +1399,9 @@ def leave(self) -> tuple[list[Register], list[RuntimeArg], list[BasicBlock], RTy ret_type = self.ret_types.pop() fn_info = self.fn_infos.pop() self.nonlocal_control.pop() + self.expression_depth = self.expression_depth_stack.pop() + self.reassigned_in_expr = self.reassigned_in_expr_stack.pop() + self.expr_has_suspend = self.expr_has_suspend_stack.pop() self.builder = self.builders[-1] self.fn_info = self.fn_infos[-1] return builder.args, runtime_args, builder.blocks, ret_type, fn_info @@ -1380,6 +1437,27 @@ def enter_scope(self, fn_info: FuncInfo) -> Iterator[None]: self.builder = self.builders[-1] self.fn_info = self.fn_infos[-1] + @contextmanager + def enter_borrow_scope(self, line: int) -> Iterator[None]: + """Enter new borrow scope from which borrows can't leak to outer expressions. + + This is a borrow region (see LowLevelIRBuilder.borrow_region) that also + resets the per-expression borrowing heuristic state, since the body forms + its own top-level expression context (e.g. a comprehension iteration or a + lambda body). + """ + old_expression_depth = self.expression_depth + old_reassigned_in_expr = self.reassigned_in_expr + old_expr_has_suspend = self.expr_has_suspend + self.expression_depth = 0 + try: + with self.builder.borrow_region(line): + yield + finally: + self.expression_depth = old_expression_depth + self.reassigned_in_expr = old_reassigned_in_expr + self.expr_has_suspend = old_expr_has_suspend + @contextmanager def enter_method( self, @@ -1556,6 +1634,46 @@ def is_native_attr_ref(self, expr: MemberExpr) -> bool: and any(expr.name in ir.attributes for ir in obj_rtype.class_ir.mro) ) + def is_final_native_attr_ref(self, expr: MemberExpr) -> bool: + """Is expr a direct reference to a Final native (struct) attribute of an instance? + + A Final attribute is read-only at runtime (it has no setter), so it can never be + reassigned after construction. This makes it safe to borrow even on free-threaded + builds, since no concurrent store can invalidate the borrowed reference. + """ + obj_rtype = self.node_type(expr.expr) + return ( + isinstance(obj_rtype, RInstance) + and obj_rtype.class_ir.is_ext_class + and obj_rtype.class_ir.is_final_attr(expr.name) + ) + + def root_is_reassigned(self, v: Value) -> bool: + """Is the root local variable a borrow chain 'v' reads from reassigned this expression? + + A whole-expression borrow of an attribute keeps the borrow root alive only + via the register holding it. If that register belongs to a local variable + that is rebound (via a walrus assignment) during the same top-level + expression, the old value may be freed while the borrow is still live. + """ + if not self.reassigned_in_expr: + return False + # Peel borrowed links back to the root value the chain reads from. + while True: + if isinstance(v, GetAttr) and v.is_borrowed: + v = v.obj + elif isinstance(v, Cast) and v.is_borrowed: + v = v.src + else: + break + if not isinstance(v, Register): + return False + for symbol in self.reassigned_in_expr: + target = self.symtables[-1].get(symbol) + if isinstance(target, AssignmentTargetRegister) and target.register is v: + return True + return False + def mark_block_unreachable(self) -> None: """Mark statements in the innermost block being processed as unreachable. @@ -1670,6 +1788,78 @@ def get_call_target_fullname(ref: RefExpr) -> str: return ref.fullname +class WalrusTargetCollector(TraverserVisitor): + """Collect the symbols assigned to by walrus expressions in a subtree.""" + + def __init__(self) -> None: + self.targets: set[SymbolNode] = set() + + def visit_assignment_expr(self, o: AssignmentExpr) -> None: + if o.target.node is not None: + self.targets.add(o.target.node) + super().visit_assignment_expr(o) + + def visit_lambda_expr(self, o: LambdaExpr) -> None: + # A lambda body forms its own expression context, so don't descend into it. + pass + + +def find_walrus_targets(expr: Expression) -> set[SymbolNode]: + """Return the symbols reassigned via a walrus expression within 'expr'. + + Walrus (':=') is the only way to rebind a variable in the middle of evaluating + an expression, so this is the complete set of in-expression reassignments. + """ + collector = WalrusTargetCollector() + expr.accept(collector) + return collector.targets + + +class SuspendDetector(TraverserVisitor): + """Detect await/yield/yield from expressions in a subtree.""" + + def __init__(self) -> None: + self.found = False + + def visit_await_expr(self, o: AwaitExpr) -> None: + self.found = True + + def visit_yield_expr(self, o: YieldExpr) -> None: + self.found = True + + def visit_yield_from_expr(self, o: YieldFromExpr) -> None: + self.found = True + + def visit_generator_expr(self, o: GeneratorExpr) -> None: + # An 'async for' clause suspends via an implicit await on __anext__ that + # isn't represented as an AwaitExpr node in the AST (list/set comprehensions + # delegate to a GeneratorExpr, so they are covered here too). + if any(o.is_async): + self.found = True + super().visit_generator_expr(o) + + def visit_dictionary_comprehension(self, o: DictionaryComprehension) -> None: + if any(o.is_async): + self.found = True + super().visit_dictionary_comprehension(o) + + def visit_lambda_expr(self, o: LambdaExpr) -> None: + # A lambda body forms its own function (and suspension) context. + pass + + +def expr_has_suspend(expr: Expression) -> bool: + """Does evaluating 'expr' involve a suspension point (await/yield/yield from)? + + A whole-expression borrow can't safely span a suspension point, since the + borrowed value and its borrow root are held in registers that aren't spilled + into the generator environment across the suspend. + """ + detector = SuspendDetector() + expr.accept(detector) + return detector.found + + def create_type_params( builder: IRBuilder, typing_mod: Value, type_args: list[TypeParam], line: int ) -> list[Value]: diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index f5d094d142317..4fd9a2e2cc748 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -7,6 +7,7 @@ from typing import Final from mypy.nodes import ( + ARG_POS, EXCLUDED_ENUM_ATTRIBUTES, TYPE_VAR_TUPLE_KIND, AssignmentStmt, @@ -21,15 +22,15 @@ NameExpr, OverloadedFuncDef, PassStmt, - RefExpr, StrExpr, TempNode, TypeInfo, TypeParam, + Var, is_class_var, ) from mypy.types import Instance, UnboundType, get_proper_type -from mypyc.common import PROPSET_PREFIX +from mypyc.common import MYPYC_DEFAULTS_SETUP, PROPSET_PREFIX from mypyc.ir.class_ir import ClassIR, NonExtClassInfo from mypyc.ir.func_ir import FuncDecl, FuncSignature from mypyc.ir.ops import ( @@ -48,15 +49,7 @@ TupleSet, Value, ) -from mypyc.ir.rtypes import ( - RType, - bool_rprimitive, - dict_rprimitive, - is_none_rprimitive, - is_object_rprimitive, - is_optional_type, - object_rprimitive, -) +from mypyc.ir.rtypes import RType, bool_rprimitive, dict_rprimitive, object_rprimitive from mypyc.irbuild.builder import IRBuilder, create_type_params from mypyc.irbuild.function import ( gen_property_getter_ir, @@ -66,7 +59,13 @@ load_type, ) from mypyc.irbuild.prepare import GENERATOR_HELPER_NAME -from mypyc.irbuild.util import dataclass_type, get_func_def, is_constant, is_dataclass_decorator +from mypyc.irbuild.util import ( + dataclass_type, + default_attr_name, + get_func_def, + is_constant, + is_dataclass_decorator, +) from mypyc.primitives.dict_ops import dict_new_op, exact_dict_set_item_op from mypyc.primitives.generic_ops import ( iter_op, @@ -188,7 +187,8 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None: cls_builder.add_attr(lvalue, stmt) # Track this ClassVar so subsequent class body statements can reference it. if is_class_var(lvalue) or stmt.is_final_def: - builder.class_body_classvars[lvalue.name] = None + assert isinstance(lvalue.node, Var), lvalue.node + builder.class_body_classvars[lvalue.node] = None elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): # Docstring. Ignore @@ -322,10 +322,6 @@ def __init__(self, builder: IRBuilder, cdef: ClassDef) -> None: def class_body_obj(self) -> Value | None: return self.type_obj - def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool: - """Controls whether to skip generating a default for an attribute.""" - return False - def add_method(self, fdef: FuncDef) -> None: handle_ext_method(self.builder, self.cdef, fdef) @@ -348,11 +344,18 @@ def finalize(self, ir: ClassIR) -> None: # Call __init_subclass__ after class attributes have been set self.builder.call_c(py_init_subclass_op, [self.type_obj], self.cdef.line) - attrs_with_defaults, default_assignments = find_attr_initializers( - self.builder, self.cdef, self.skip_attr_default - ) - ir.attrs_with_defaults.update(attrs_with_defaults) - generate_attr_defaults_init(self.builder, self.cdef, default_assignments) + # Under separate compilation, prepare.py pre-registers the decl iff + # the class has its own default attribute assignments to emit, so we + # can skip the body walk entirely when it isn't present. Without + # separate compilation, find_attr_initializers walks the MRO so that + # inherited defaults are reflected in ir.attrs_with_defaults (relied + # on by the attribute-definedness analysis), so we always run it. + if not self.builder.options.separate or MYPYC_DEFAULTS_SETUP in ir.method_decls: + attrs_with_defaults, default_assignments = find_attr_initializers( + self.builder, self.cdef + ) + ir.attrs_with_defaults.update(attrs_with_defaults) + generate_attr_defaults_init(self.builder, self.cdef, default_assignments) create_ne_from_eq(self.builder, self.cdef) @@ -380,9 +383,6 @@ def create_non_ext_info(self) -> NonExtClassInfo: self.builder.add(LoadAddress(type_object_op.type, type_object_op.src, self.cdef.line)), ) - def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool: - return stmt.type is not None - def get_type_annotation(self, stmt: AssignmentStmt) -> TypeInfo | None: # We populate __annotations__ because dataclasses uses it to determine # which attributes to compute on. @@ -445,9 +445,6 @@ class AttrsClassBuilder(DataClassBuilder): add_annotations_to_dict = False - def skip_attr_default(self, name: str, stmt: AssignmentStmt) -> bool: - return True - def get_type_annotation(self, stmt: AssignmentStmt) -> TypeInfo | None: if isinstance(stmt.rvalue, CallExpr): # find the type arg in `attr.ib(type=str)` @@ -741,58 +738,50 @@ def add_non_ext_class_attr( def find_attr_initializers( - builder: IRBuilder, cdef: ClassDef, skip: Callable[[str, AssignmentStmt], bool] | None = None + builder: IRBuilder, cdef: ClassDef ) -> tuple[set[str], list[tuple[AssignmentStmt, str]]]: """Find initializers of attributes in a class body. - If provided, the skip arg should be a callable which will return whether - to skip generating a default for an attribute. It will be passed the name of - the attribute and the corresponding AssignmentStmt. + Under separate compilation, only this class's own body is walked, and + generate_attr_defaults_init emits a runtime call to the parent's + __mypyc_defaults_setup so inherited defaults are produced by chaining, + not by inlining. Walking the MRO here would break under separate=True + with mypy's incremental cache: a base class loaded from the cache has + an empty ClassDef.defs.body (mypy/nodes.py::ClassDef.serialize doesn't + serialize the class body), so inherited assignments would be silently + dropped and the subclass's __mypyc_defaults_setup would leave inherited + slots in the "undefined" state at runtime. + + Without separate compilation, all modules are parsed in the same pass + and the MRO walk is safe; we keep the original inline-all behavior + there as an optimization (no chain call needed for instance creation). """ cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return set(), [] - attrs_with_defaults = set() + cls_type = dataclass_type(cdef) + attrs_with_defaults: set[str] = set() + default_assignments: list[tuple[AssignmentStmt, str]] = [] - # Pull out all assignments in classes in the mro so we can initialize them # TODO: Support nested statements - default_assignments: list[tuple[AssignmentStmt, str]] = [] - for info in reversed(cdef.info.mro): - if info not in builder.mapper.type_to_ir: + if builder.options.separate: + infos: list[TypeInfo] = [cdef.info] + else: + infos = list(reversed(cdef.info.mro)) + + for info in infos: + info_ir = builder.mapper.type_to_ir.get(info) + if info_ir is None: continue for stmt in info.defn.defs.body: - if ( - isinstance(stmt, AssignmentStmt) - and isinstance(stmt.lvalues[0], NameExpr) - and not is_class_var(stmt.lvalues[0]) - and not isinstance(stmt.rvalue, TempNode) - ): - name = stmt.lvalues[0].name - if name == "__slots__": - continue - - if name == "__deletable__": - check_deletable_declaration(builder, cls, stmt.line) - continue - - if skip is not None and skip(name, stmt): - continue - - attr_type = cls.attr_type(name) - - # If the attribute is initialized to None and type isn't optional, - # doesn't initialize it to anything (special case for "# type:" comments). - if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == "builtins.None": - if ( - not is_optional_type(attr_type) - and not is_object_rprimitive(attr_type) - and not is_none_rprimitive(attr_type) - ): - continue - - attrs_with_defaults.add(name) - default_assignments.append((stmt, info.module_name)) + if not isinstance(stmt, AssignmentStmt): + continue + name = default_attr_name(stmt, info_ir, cls_type) + if name is None: + continue + attrs_with_defaults.add(name) + default_assignments.append((stmt, info.module_name)) return attrs_with_defaults, default_assignments @@ -800,15 +789,49 @@ def find_attr_initializers( def generate_attr_defaults_init( builder: IRBuilder, cdef: ClassDef, default_assignments: list[tuple[AssignmentStmt, str]] ) -> None: - """Generate an initialization method for default attr values (from class vars).""" - if not default_assignments: - return + """Generate an initialization method for default attr values (from class vars). + + Under separate compilation, the emitted __mypyc_defaults_setup chains to + the nearest ancestor that has the method (Python __init__ style), then + sets only this class's own defaults; inherited defaults are produced by + the chain at runtime. The ancestor lookup uses cls.mro[1:] and relies on + prepare.py having registered the FuncDecl on every class that needs one + before any IR build runs. IR build within a compilation group proceeds + in filename order, so this class may be IR-built before its base, and a + method_decls lookup that depended on the base having been IR-built first + would miss. Without separate compilation, find_attr_initializers has + already collected the full MRO's defaults into default_assignments, so + we inline them all as before. + """ cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return - with builder.enter_method(cls, "__mypyc_defaults_setup", bool_rprimitive): + parent_with_defaults: ClassIR | None = None + if builder.options.separate: + for ancestor in cls.mro[1:]: + if MYPYC_DEFAULTS_SETUP in ancestor.method_decls: + parent_with_defaults = ancestor + break + + if not default_assignments and parent_with_defaults is None: + return + + with builder.enter_method(cls, MYPYC_DEFAULTS_SETUP, bool_rprimitive): self_var = builder.self() + + # Chain to parent's setup so inherited defaults run first; propagate + # its False return so a parent default that raised still aborts + # instance creation rather than being silently swallowed here. + if parent_with_defaults is not None: + decl = parent_with_defaults.method_decl(MYPYC_DEFAULTS_SETUP) + parent_ok = builder.builder.call(decl, [self_var], [ARG_POS], [None], cdef.line) + fail_block, continue_block = BasicBlock(), BasicBlock() + builder.add(Branch(parent_ok, continue_block, fail_block, Branch.BOOL)) + builder.activate_block(fail_block) + builder.add(Return(builder.false())) + builder.activate_block(continue_block) + for stmt, origin_module in default_assignments: lvalue = stmt.lvalues[0] assert isinstance(lvalue, NameExpr), lvalue @@ -833,26 +856,6 @@ def generate_attr_defaults_init( builder.add(Return(builder.true())) -def check_deletable_declaration(builder: IRBuilder, cl: ClassIR, line: int) -> None: - for attr in cl.deletable: - if attr not in cl.attributes: - if not cl.has_attr(attr): - builder.error(f'Attribute "{attr}" not defined', line) - continue - for base in cl.mro: - if attr in base.property_types: - builder.error(f'Cannot make property "{attr}" deletable', line) - break - else: - _, base = cl.attr_details(attr) - builder.error( - ('Attribute "{}" not defined in "{}" ' + '(defined in "{}")').format( - attr, cl.name, base.name - ), - line, - ) - - def create_ne_from_eq(builder: IRBuilder, cdef: ClassDef) -> None: """Create a "__ne__" method from a "__eq__" method (if only latter exists).""" cls = builder.mapper.type_to_ir[cdef.info] @@ -862,12 +865,10 @@ def create_ne_from_eq(builder: IRBuilder, cdef: ClassDef) -> None: def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> None: """Generate a "__ne__" method from a "__eq__" method.""" - func_ir = cls.get_method("__eq__") - assert func_ir - eq_sig = func_ir.decl.sig + eq_sig = cls.method_sig("__eq__") strict_typing = builder.options.strict_dunders_typing with builder.enter_method(cls, "__ne__", eq_sig.ret_type): - rhs_type = eq_sig.args[0].type if strict_typing else object_rprimitive + rhs_type = eq_sig.args[1].type rhs_arg = builder.add_argument("rhs", rhs_type) eqval = builder.add(MethodCall(builder.self(), "__eq__", [rhs_arg], line)) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index e8d22a051cc4d..91e1e55d8da26 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -58,7 +58,12 @@ TypeType, get_proper_type, ) -from mypyc.common import MAX_SHORT_INT +from mypyc.common import ( + IS_FREE_THREADED, + KEEP_ALIVE_SHORT_LIVED, + KEEP_ALIVE_WHOLE_EXPRESSION, + MAX_SHORT_INT, +) from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD from mypyc.ir.ops import ( @@ -66,11 +71,14 @@ Assign, BasicBlock, CallC, + Cast, ComparisonOp, + GetAttr, Integer, LoadAddress, LoadLiteral, PrimitiveDescription, + PrimitiveOp, RaiseStandardError, Register, TupleGet, @@ -93,9 +101,12 @@ is_list_rprimitive, is_none_rprimitive, is_object_rprimitive, + is_str_rprimitive, + is_tagged, is_tuple_rprimitive, object_rprimitive, set_rprimitive, + short_int_rprimitive, vec_api_by_item_type, ) from mypyc.irbuild.ast_helpers import is_borrow_friendly_expr, process_conditional @@ -119,6 +130,7 @@ apply_dunder_specialization, apply_function_specialization, apply_method_specialization, + translate_getitem_with_bounds_check, translate_object_new, translate_object_setattr, ) @@ -137,7 +149,12 @@ from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op from mypyc.primitives.misc_ops import ellipsis_op, get_module_dict_op, new_slice_op, type_op from mypyc.primitives.set_ops import set_add_op, set_in_op, set_update_op -from mypyc.primitives.str_ops import str_slice_op +from mypyc.primitives.str_ops import ( + str_adjust_index_op, + str_get_item_unsafe_as_int_op, + str_range_check_op, + str_slice_op, +) from mypyc.primitives.tuple_ops import list_tuple_op, tuple_slice_op # Name and attribute references @@ -223,7 +240,11 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: # If we're evaluating a class body and this name is a ClassVar defined earlier # in the same class, load it from the class being built (type object for ext classes, # class dict for non-ext classes) instead of module globals. - if builder.class_body_obj is not None and expr.name in builder.class_body_classvars: + if ( + builder.class_body_obj is not None + and isinstance(expr.node, Var) + and expr.node in builder.class_body_classvars + ): if builder.class_body_ir is not None and builder.class_body_ir.is_ext_class: return builder.py_get_attr(builder.class_body_obj, expr.name, expr.line) else: @@ -239,7 +260,7 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: if expr.fullname in ("typing.TYPE_CHECKING", "typing_extensions.TYPE_CHECKING"): return builder.false(expr.line) - # First check if this is maybe a final attribute. + # First check if this is maybe a final class/module attribute. final = builder.get_final_ref(expr) if final is not None: fullname, final_var, native = final @@ -259,9 +280,16 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports: return builder.load_module(expr.node.fullname) - can_borrow = builder.is_native_attr_ref(expr) - obj = builder.accept(expr.expr, can_borrow=can_borrow) rtype = builder.node_type(expr) + # Borrowing a native attribute read is unsafe on free-threaded builds, since another + # thread could concurrently reassign the attribute and free the old value. We still borrow + # in two cases: + # - Native Final attributes are read-only at runtime, so they can never be reassigned. + # - Vec-typed attributes require manual synchronization, so we borrow them liberally. + can_borrow = builder.is_native_attr_ref(expr) and ( + not IS_FREE_THREADED or isinstance(rtype, RVec) or builder.is_final_native_attr_ref(expr) + ) + obj = builder.accept(expr.expr, can_borrow=can_borrow) if ( is_object_rprimitive(obj.type) @@ -285,8 +313,43 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: check_instance_attribute_access_through_class(builder, expr, typ) - borrow = can_borrow and builder.can_borrow - return builder.builder.get_attr(obj, expr.name, rtype, expr.line, borrow=borrow) + is_final = builder.is_final_native_attr_ref(expr) + scope = KEEP_ALIVE_SHORT_LIVED + if ( + is_final + and builder.expression_depth > 1 + and value_borrow_scope(builder, obj) >= KEEP_ALIVE_WHOLE_EXPRESSION + # Don't borrow across the whole expression if the borrow root can be + # rebound via a walrus assignment + and not builder.root_is_reassigned(obj) + # Don't borrow across a suspension point (await/yield/yield from), since + # the borrow would not survive the suspend (registers aren't spilled). + and not builder.expr_has_suspend + ): + scope = KEEP_ALIVE_WHOLE_EXPRESSION + borrow = (can_borrow and builder.can_borrow) or scope == KEEP_ALIVE_WHOLE_EXPRESSION + return builder.builder.get_attr( + obj, expr.name, rtype, expr.line, borrow=borrow, borrow_scope=scope + ) + + +def value_borrow_scope(builder: IRBuilder, v: Value) -> int: + """Compute how long an existing borrowed value can safely be kept alive. + + Returns a KEEP_ALIVE_* constant (or a large value if 'v' is not borrowed and + thus has no borrowing constraint). + """ + if isinstance(v, GetAttr) and v.is_borrowed: + return min(v.borrow_scope, value_borrow_scope(builder, v.obj)) + elif isinstance(v, Cast) and v.is_borrowed: + # A cast just propagates the value (when successful), so the scope is + # determined by the source. + return value_borrow_scope(builder, v.src) + elif isinstance(v, (CallC, PrimitiveOp)) and v.is_borrowed: + # Values borrowed from a C function (e.g. a borrowed list/vec item) may be + # invalidated by arbitrary computation, so they are only short-lived. + return KEEP_ALIVE_SHORT_LIVED + return 999 def check_instance_attribute_access_through_class( @@ -760,8 +823,14 @@ def try_optimize_int_floor_divide(builder: IRBuilder, expr: OpExpr) -> OpExpr: def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: index = expr.index base_type = builder.node_type(expr.base) - can_borrow = is_list_rprimitive(base_type) or isinstance(base_type, RVec) - can_borrow_base = can_borrow and is_borrow_friendly_expr(builder, index) + # We can borrow a list item safely only if GIL is enabled. The vec type is optimized for + # performance, so we'll do unsafe borrowing. + can_borrow = (is_list_rprimitive(base_type) and not IS_FREE_THREADED) or isinstance( + base_type, RVec + ) + can_borrow_base = ( + is_list_rprimitive(base_type) or isinstance(base_type, RVec) + ) and is_borrow_friendly_expr(builder, index) # Check for dunder specialization for non-slice indexing if not isinstance(index, SliceExpr): @@ -783,7 +852,7 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: if value: return value - index_reg = builder.accept(expr.index, can_borrow=can_borrow) + index_reg = builder.accept(expr.index, can_borrow=can_borrow or can_borrow_base) return builder.builder.get_item( base, index_reg, builder.node_type(expr), expr.line, can_borrow=builder.can_borrow ) @@ -849,15 +918,17 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val target = Register(expr_type) builder.activate_block(if_body) - true_value = builder.accept(expr.if_expr) - true_value = builder.coerce(true_value, expr_type, expr.line) - builder.add(Assign(target, true_value, expr.line)) + with builder.builder.borrow_region(expr.line): + true_value = builder.accept(expr.if_expr) + true_value = builder.coerce(true_value, expr_type, expr.line) + builder.add(Assign(target, true_value, expr.line)) builder.goto(next_block) builder.activate_block(else_body) - false_value = builder.accept(expr.else_expr) - false_value = builder.coerce(false_value, expr_type, expr.line) - builder.add(Assign(target, false_value, expr.line)) + with builder.builder.borrow_region(expr.line): + false_value = builder.accept(expr.else_expr) + false_value = builder.coerce(false_value, expr_type, expr.line) + builder.add(Assign(target, false_value, expr.line)) builder.goto(next_block) builder.activate_block(next_block) @@ -918,6 +989,16 @@ def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value: return result if len(e.operators) == 1: + # s[i] == 'x' / s[i] != 'x' (and the symmetric RHS) -> int compare of + # codepoints. Skips the per-iteration 1-char str allocation/lookup and + # generic str equality call. + if first_op in ("==", "!="): + result = try_specialize_str_index_compare( + builder, first_op, e.operands[0], e.operands[1], e.line + ) + if result is not None: + return result + # Special some common simple cases if first_op in ("is", "is not"): right_expr = e.operands[1] @@ -960,6 +1041,50 @@ def go(i: int, prev: Value) -> Value: return go(0, builder.accept(e.operands[0])) +def try_specialize_str_index_compare( + builder: IRBuilder, op: str, lhs: Expression, rhs: Expression, line: int +) -> Value | None: + """Specialize `s[i] == 'x'` / `s[i] != 'x'` (and the symmetric form with + operands swapped) into an int compare of codepoints. + + Returns None if the pattern doesn't match: the indexed base must be str, + the index must be an integer, and the literal must be a 1-character str. + Multi-character or empty literals fall through to the generic str compare + (which still returns False for them, matching today's behavior). + """ + # Normalize so the IndexExpr is on the left. + if isinstance(rhs, IndexExpr) and not isinstance(lhs, IndexExpr): + tmp = lhs + lhs, rhs = rhs, tmp + # Shape: s[i] {==, !=} "x" where "x" is exactly one codepoint. + if ( + not isinstance(lhs, IndexExpr) + or not isinstance(rhs, StrExpr) + or len(rhs.value) != 1 + or not is_str_rprimitive(builder.node_type(lhs.base)) + ): + return None + index_type = builder.node_type(lhs.index) + if not (is_tagged(index_type) or is_fixed_width_rtype(index_type)): + return None + + # ord(s[i]) with bounds check; raises IndexError for out-of-range indices, + # matching the behavior of the generic s[i] path. + codepoint = translate_getitem_with_bounds_check( + builder, + lhs.base, + [lhs.index], + lhs, + str_adjust_index_op, + str_range_check_op, + str_get_item_unsafe_as_int_op, + ) + if codepoint is None: + return None + literal_cp = Integer(ord(rhs.value), short_int_rprimitive, line) + return builder.binary_op(codepoint, literal_cp, op, line) + + def try_specialize_in_expr( builder: IRBuilder, op: str, lhs: Expression, rhs: Expression, line: int ) -> Value | None: diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 95894dcedaba8..5a36dcb80ff7d 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -28,6 +28,7 @@ Var, ) from mypy.types import LiteralType, TupleType, get_proper_type, get_proper_types +from mypyc.common import IS_FREE_THREADED from mypyc.ir.ops import ( ERR_NEVER, BasicBlock, @@ -81,7 +82,12 @@ ) from mypyc.primitives.exc_ops import no_err_occurred_op, propagate_if_error_op from mypyc.primitives.generic_ops import aiter_op, anext_op, iter_op, next_op -from mypyc.primitives.list_ops import list_append_op, list_get_item_unsafe_op, new_list_set_item_op +from mypyc.primitives.list_ops import ( + list_append_op, + list_get_item_int64_op, + list_get_item_unsafe_op, + new_list_set_item_op, +) from mypyc.primitives.misc_ops import stop_async_iteration_op from mypyc.primitives.registry import CFunctionDescription from mypyc.primitives.set_ops import set_add_op @@ -287,7 +293,8 @@ def sequence_from_generator_preallocate_helper( target_op = empty_op_llbuilder(length, line) def set_item(item_index: Value) -> None: - e = builder.accept(gen.left_expr) + with builder.enter_borrow_scope(line): + e = builder.accept(gen.left_expr) set_item_op(target_op, item_index, e, line) for_loop_helper_with_index( @@ -440,9 +447,10 @@ def loop_contents( remaining_loop_params: the parameters for any further nested loops; if it's empty we'll instead evaluate the "gen_inner_stmts" function """ - # Check conditions, in order, short circuiting them. + # Check conditions, in order, short-circuiting them. for cond in conds: - cond_val = builder.accept(cond) + with builder.enter_borrow_scope(line): + cond_val = builder.accept(cond) cont_block, rest_block = BasicBlock(), BasicBlock() # If the condition is true we'll skip the continue. builder.add_bool_branch(cond_val, rest_block, cont_block) @@ -456,7 +464,8 @@ def loop_contents( else: # We finally reached the actual body of the generator. # Generate the IR for the inner loop body. - gen_inner_stmts() + with builder.enter_borrow_scope(line): + gen_inner_stmts() handle_loop(loop_params) @@ -866,7 +875,10 @@ def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> # since we want to use __getitem__ if we don't have an unsafe version, # so we just check manually. if is_list_rprimitive(target.type): - return builder.primitive_op(list_get_item_unsafe_op, [target, index], line) + if not IS_FREE_THREADED: + return builder.primitive_op(list_get_item_unsafe_op, [target, index], line) + else: + return builder.primitive_op(list_get_item_int64_op, [target, index], line) elif is_tuple_rprimitive(target.type): return builder.call_c(tuple_get_item_unsafe_op, [target, index], line) elif is_str_rprimitive(target.type): diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index c19eded77464e..aafdb73fed84e 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -7,7 +7,8 @@ from __future__ import annotations import sys -from collections.abc import Callable, Sequence +from collections.abc import Callable, Iterator, Sequence +from contextlib import contextmanager from typing import Final, TypeGuard, cast from mypy.argmap import map_actuals_to_formals @@ -19,6 +20,7 @@ FAST_ISINSTANCE_MAX_SUBCLASSES, FAST_PREFIX, IS_FREE_THREADED, + KEEP_ALIVE_SHORT_LIVED, MAX_LITERAL_SHORT_INT, MAX_SHORT_INT, MIN_LITERAL_SHORT_INT, @@ -206,6 +208,7 @@ new_tuple_with_length_op, sequence_tuple_op, ) +from mypyc.rt_expandtype import expand_rtype from mypyc.rt_subtype import is_runtime_subtype from mypyc.sametype import is_same_type from mypyc.subtype import is_subtype @@ -249,6 +252,15 @@ BOOL_BINARY_OPS: Final = {"&", "&=", "|", "|=", "^", "^=", "==", "!=", "<", "<=", ">", ">="} +class PendingKeepAlive: + __slots__ = ("value", "scope", "seq") + + def __init__(self, value: Value, scope: int, seq: int) -> None: + self.value = value + self.scope = scope + self.seq = seq + + class LowLevelIRBuilder: """A "low-level" IR builder class. @@ -275,7 +287,10 @@ def __init__(self, errors: Errors | None, options: CompilerOptions) -> None: self.error_handlers: list[BasicBlock | None] = [None] # Values that we need to keep alive as long as we have borrowed # temporaries. Use flush_keep_alives() to mark the end of the live range. - self.keep_alives: list[Value] = [] + # The second value is the scope/duration of keep alive (KEEP_ALIVE_* constant). + # Different values must be kept alive for different durations. + self.keep_alives: list[PendingKeepAlive] = [] + self.next_keep_alive_seq = 0 def set_module(self, module_name: str, module_path: str) -> None: """Set the name and path of the current module.""" @@ -366,10 +381,55 @@ def self(self) -> Register: """ return self.args[0] - def flush_keep_alives(self, line: int) -> None: - if self.keep_alives: - self.add(KeepAlive(self.keep_alives.copy(), line)) - self.keep_alives = [] + def flush_keep_alives(self, line: int, *, scope: int = KEEP_ALIVE_SHORT_LIVED) -> None: + if any(entry.scope == scope for entry in self.keep_alives): + self.add( + KeepAlive( + [entry.value for entry in self.keep_alives if entry.scope == scope], line + ) + ) + self.keep_alives = [entry for entry in self.keep_alives if entry.scope != scope] + + def keep_alive_checkpoint(self) -> int: + return self.next_keep_alive_seq + + def flush_keep_alives_since(self, line: int, checkpoint: int) -> None: + """Flush keep-alives added after 'checkpoint' without touching earlier ones.""" + new_keep_alives = [entry for entry in self.keep_alives if entry.seq >= checkpoint] + if new_keep_alives: + self.add(KeepAlive([entry.value for entry in new_keep_alives], line)) + self.keep_alives = [entry for entry in self.keep_alives if entry.seq < checkpoint] + + @contextmanager + def borrow_region(self, line: int) -> Iterator[None]: + """Confine borrows created in this region: flush them when leaving it. + + Keep-alives added inside the region (regardless of their duration scope) + are flushed on exit, so borrows can't leak past a lexical boundary such as + a conditional branch, an 'and'/'or' branch or a comprehension iteration. + """ + checkpoint = self.keep_alive_checkpoint() + yield + self.flush_keep_alives_since(line, checkpoint) + + def add_keep_alive(self, value: Value, scope: int) -> None: + self.keep_alives.append(PendingKeepAlive(value, scope, self.next_keep_alive_seq)) + self.next_keep_alive_seq += 1 + + def keep_borrow_source_alive(self, value: Value, borrow_scope: int) -> None: + """Ensure the source of borrowed value will be alive for given borrow scope. + + A borrow keeps reading from 'value', so 'value' must stay valid for the + borrow's whole scope. When 'value' is itself a borrowed cast it holds no + reference of its own (and coerce() only kept its source alive short-term), + so we follow the cast to the underlying owned value and keep *that* alive + for the requested scope too. Otherwise, a longer-lived borrow through a cast + (e.g. a Final attribute read via cast(T, make())) could read freed memory. + """ + self.add_keep_alive(value, borrow_scope) + while isinstance(value, Cast) and value.is_borrowed: + value = value.src + self.add_keep_alive(value, borrow_scope) def debug_print(self, toprint: str | Value) -> None: if isinstance(toprint, str): @@ -399,7 +459,7 @@ def unbox_or_cast( return self.add(Unbox(src, target_type, line)) else: if can_borrow: - self.keep_alives.append(src) + self.add_keep_alive(src, KEEP_ALIVE_SHORT_LIVED) return self.add(Cast(src, target_type, line, borrow=can_borrow, unchecked=unchecked)) def coerce( @@ -804,19 +864,32 @@ def coerce_nullable(self, src: Value, target_type: RType, line: int) -> Value: # Attribute access def get_attr( - self, obj: Value, attr: str, result_type: RType, line: int, *, borrow: bool = False + self, + obj: Value, + attr: str, + result_type: RType, + line: int, + *, + borrow: bool = False, + borrow_scope: int = KEEP_ALIVE_SHORT_LIVED, ) -> Value: - """Get a native or Python attribute of an object.""" + """Get a native or Python attribute of an object. + + If the result is borrowed, borrow_scope controls how long it stays valid + (a KEEP_ALIVE_* constant). The caller is responsible for choosing a scope + that is actually safe (e.g. downgrading to short-lived if the borrow root + may be rebound during the borrow's live range). + """ if ( isinstance(obj.type, RInstance) and obj.type.class_ir.is_ext_class and obj.type.class_ir.has_attr(attr) ): - op = GetAttr(obj, attr, line, borrow=borrow) + op = GetAttr(obj, attr, line, borrow=borrow, borrow_scope=borrow_scope) # For non-refcounted attribute types, the borrow might be # disabled even if requested, so don't check 'borrow'. if op.is_borrowed: - self.keep_alives.append(obj) + self.keep_borrow_source_alive(obj, borrow_scope) return self.add(op) elif isinstance(obj.type, RUnion): return self.union_get_attr(obj, obj.type, attr, result_type, line) @@ -2111,18 +2184,20 @@ def shortcircuit_helper( # it is the right side if the left is false. true_body, false_body = (right_body, left_body) if op == "and" else (left_body, right_body) - left_value = left() - self.add_bool_branch(left_value, true_body, false_body) + with self.borrow_region(line): + left_value = left() + self.add_bool_branch(left_value, true_body, false_body) - self.activate_block(left_body) - left_coerced = self.coerce(left_value, expr_type, line) - self.add(Assign(target, left_coerced, line)) + self.activate_block(left_body) + left_coerced = self.coerce(left_value, expr_type, line) + self.add(Assign(target, left_coerced, line)) self.goto(next_block) self.activate_block(right_body) - right_value = right() - right_coerced = self.coerce(right_value, expr_type, line) - self.add(Assign(target, right_coerced, line)) + with self.borrow_region(line): + right_value = right() + right_coerced = self.coerce(right_value, expr_type, line) + self.add(Assign(target, right_coerced, line)) self.goto(next_block) self.activate_block(next_block) @@ -2280,7 +2355,7 @@ def call_c( # immediately freed, at the risk of a dangling pointer. for arg in coerced: if not isinstance(arg, (Integer, LoadLiteral)): - self.keep_alives.append(arg) + self.add_keep_alive(arg, KEEP_ALIVE_SHORT_LIVED) if desc.error_kind == ERR_NEG_INT: comp = ComparisonOp(target, Integer(0, desc.return_type, line), ComparisonOp.SGE, line) comp.error_kind = ERR_FALSE @@ -2334,6 +2409,7 @@ def primitive_op( args: list[Value], line: int, result_type: RType | None = None, + type_args: list[RType] | None = None, ) -> Value: """Add a primitive op.""" # Does this primitive map into calling a Python C API @@ -2363,10 +2439,20 @@ def primitive_op( # This primitive gets transformed in a lowering pass to # lower-level IR ops using a custom transform function. + # Evaluate argument and return types for generic primitives + return_type = None + if desc.type_params is not None: + assert type_args is not None, "Generic primitive op requires explicit type arguments" + assert len(type_args) == len(desc.type_params) + arg_types = [expand_rtype(arg_type, type_args) for arg_type in desc.arg_types] + return_type = expand_rtype(desc.return_type, type_args) + else: + arg_types = desc.arg_types + coerced = [] # Coerce fixed number arguments - for i in range(min(len(args), len(desc.arg_types))): - formal_type = desc.arg_types[i] + for i in range(min(len(args), len(arg_types))): + formal_type = arg_types[i] arg = args[i] assert formal_type is not None # TODO arg = self.coerce(arg, formal_type, line) @@ -2374,14 +2460,23 @@ def primitive_op( assert desc.ordering is None assert desc.var_arg_type is None assert not desc.extra_int_constants - target = self.add(PrimitiveOp(coerced, desc, line=line)) + target = self.add( + PrimitiveOp( + coerced, + desc, + line=line, + arg_types=arg_types, + return_type=return_type, + type_args=type_args, + ) + ) if desc.is_borrowed: # If the result is borrowed, force the arguments to be # kept alive afterwards, as otherwise the result might be # immediately freed, at the risk of a dangling pointer. for arg in coerced: if not isinstance(arg, (Integer, LoadLiteral)): - self.keep_alives.append(arg) + self.add_keep_alive(arg, KEEP_ALIVE_SHORT_LIVED) if desc.error_kind == ERR_NEG_INT: comp = ComparisonOp(target, Integer(0, desc.return_type, line), ComparisonOp.SGE, line) comp.error_kind = ERR_FALSE diff --git a/mypyc/irbuild/prepare.py b/mypyc/irbuild/prepare.py index 09bfc8339b404..6e697823593a6 100644 --- a/mypyc/irbuild/prepare.py +++ b/mypyc/irbuild/prepare.py @@ -21,6 +21,7 @@ from mypy.nodes import ( ARG_STAR, ARG_STAR2, + AssignmentStmt, CallExpr, ClassDef, Decorator, @@ -39,7 +40,13 @@ from mypy.semanal import refers_to_fullname from mypy.traverser import TraverserVisitor from mypy.types import Instance, Type, get_proper_type -from mypyc.common import FAST_PREFIX, PROPSET_PREFIX, SELF_NAME, get_id_from_name +from mypyc.common import ( + FAST_PREFIX, + MYPYC_DEFAULTS_SETUP, + PROPSET_PREFIX, + SELF_NAME, + get_id_from_name, +) from mypyc.crash import catch_errors from mypyc.errors import Errors from mypyc.ir.class_ir import ClassIR @@ -55,6 +62,7 @@ from mypyc.ir.rtypes import ( RInstance, RType, + bool_rprimitive, dict_rprimitive, none_rprimitive, object_pointer_rprimitive, @@ -63,6 +71,8 @@ ) from mypyc.irbuild.mapper import Mapper from mypyc.irbuild.util import ( + dataclass_type, + default_attr_name, get_func_def, get_mypyc_attrs, is_dataclass, @@ -131,6 +141,24 @@ def build_type_map( if class_ir.is_ext_class: prepare_implicit_property_accessors(cdef.info, class_ir, module.fullname, mapper) + # Register __mypyc_defaults_setup FuncDecls on classes that have their own + # class-level default attribute assignments. Done here, before any IR build + # runs, so that the cross-class lookup in generate_attr_defaults_init is + # order-independent: IR build within a compilation group proceeds in + # filename order, so a subclass may be IR-built before its base. + for module, cdef in classes: + class_ir = mapper.type_to_ir[cdef.info] + if class_ir.is_ext_class and _has_own_default_attrs(cdef, class_ir): + _register_defaults_setup_decl(class_ir, module.fullname) + + # Validate __deletable__ declarations. Done here so the compiler exits + # early on invalid input before any IR is built. + for module, cdef in classes: + class_ir = mapper.type_to_ir[cdef.info] + if class_ir.is_ext_class: + with catch_errors(module.path, cdef.line): + _check_deletable_declarations(module.path, cdef, class_ir, errors) + # Collect all the functions also. We collect from the symbol table # so that we can easily pick out the right copy of a function that # is conditionally defined. This doesn't include nested functions! @@ -182,7 +210,12 @@ def load_type_map(mapper: Mapper, modules: list[MypyFile], deser_ctx: DeserMaps) continue mapper.type_to_ir[node.node] = ir mapper.symbol_fullnames.add(node.node.fullname) - mapper.func_to_decl[node.node] = ir.ctor + # Trait/builtin-base classes have an ir.ctor FuncDecl + # but no emitted CPyDef_, so a cross-group direct + # call would hit an undefined symbol. Mirror the skip + # in prepare_init_method. + if not ir.is_trait and not ir.builtin_base: + mapper.func_to_decl[node.node] = ir.ctor for module in modules: for func in get_module_func_defs(module): @@ -403,6 +436,68 @@ def validate_acyclic_class_bases( ) +def _has_own_default_attrs(cdef: ClassDef, ir: ClassIR) -> bool: + """Whether this class's own body has any default attribute assignment + that would be emitted into __mypyc_defaults_setup. + + Used during prepare to decide whether to register a + __mypyc_defaults_setup FuncDecl ahead of IR build. + """ + if ir.builtin_base or ir.is_trait: + return False + cls_type = dataclass_type(cdef) + return any( + default_attr_name(stmt, ir, cls_type) is not None + for stmt in cdef.info.defn.defs.body + if isinstance(stmt, AssignmentStmt) + ) + + +def _register_defaults_setup_decl(ir: ClassIR, module_name: str) -> None: + sig = FuncSignature([RuntimeArg(SELF_NAME, RInstance(ir))], bool_rprimitive) + ir.method_decls[MYPYC_DEFAULTS_SETUP] = FuncDecl( + MYPYC_DEFAULTS_SETUP, ir.name, module_name, sig + ) + + +def _check_deletable_declarations(path: str, cdef: ClassDef, ir: ClassIR, errors: Errors) -> None: + """Validate that attributes listed in __deletable__ refer to definable + attributes on the class. + + Runs in the prepare phase so we exit early on invalid programs before + any IR is built. + """ + if not ir.deletable: + return + line = next( + ( + stmt.line + for stmt in cdef.info.defn.defs.body + if isinstance(stmt, AssignmentStmt) + and isinstance(stmt.lvalues[0], NameExpr) + and stmt.lvalues[0].name == "__deletable__" + ), + cdef.line, + ) + for attr in ir.deletable: + if attr not in ir.attributes: + if not ir.has_attr(attr): + errors.error(f'Attribute "{attr}" not defined', path, line) + continue + for base in ir.mro: + if attr in base.property_types: + errors.error(f'Cannot make property "{attr}" deletable', path, line) + break + else: + _, base = ir.attr_details(attr) + errors.error( + f'Attribute "{attr}" not defined in "{ir.name}" ' + f'(defined in "{base.name}")', + path, + line, + ) + + def prepare_class_def( path: str, module_name: str, @@ -550,6 +645,8 @@ def prepare_methods_and_attributes( add_getter_declaration(ir, name, attr_rtype, module_name) add_setter_declaration(ir, name, attr_rtype, module_name) ir.attributes[name] = attr_rtype + if node.node.is_final: + ir.final_attributes.add(name) elif isinstance(node.node, (FuncDef, Decorator)): prepare_method_def(ir, module_name, cdef, mapper, node.node, options) elif isinstance(node.node, OverloadedFuncDef): diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 428650a810dca..21f47b190c302 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -49,7 +49,7 @@ YieldExpr, YieldFromExpr, ) -from mypyc.common import TEMP_ATTR_NAME +from mypyc.common import KEEP_ALIVE_SHORT_LIVED, KEEP_ALIVE_WHOLE_EXPRESSION, TEMP_ATTR_NAME from mypyc.ir.ops import ( ERR_NEVER, NAMESPACE_MODULE, @@ -80,12 +80,19 @@ c_pyssize_t_rprimitive, exc_rtuple, is_tagged, + lock_rprimitive, none_rprimitive, object_pointer_rprimitive, object_rprimitive, ) from mypyc.irbuild.ast_helpers import is_borrow_friendly_expr, process_conditional -from mypyc.irbuild.builder import IRBuilder, create_type_params, int_borrow_friendly_op +from mypyc.irbuild.builder import ( + IRBuilder, + create_type_params, + expr_has_suspend, + find_walrus_targets, + int_borrow_friendly_op, +) from mypyc.irbuild.for_helpers import for_loop_helper from mypyc.irbuild.generator import add_raise_exception_blocks_to_generator_class from mypyc.irbuild.nonlocalcontrol import ( @@ -115,6 +122,7 @@ restore_exc_info_op, ) from mypyc.primitives.generic_ops import iter_op, next_raw_op, py_delattr_op +from mypyc.primitives.librt_threading_ops import lock_acquire_op, lock_release_op from mypyc.primitives.misc_ops import ( check_stop_op, coro_op, @@ -159,10 +167,17 @@ def transform_expression_stmt(builder: IRBuilder, stmt: ExpressionStmt) -> None: if isinstance(stmt.expr, StrExpr): # Docstring. Ignore return - # ExpressionStmts do not need to be coerced like other Expressions, so we shouldn't - # call builder.accept here. + # ExpressionStmts do not need to be coerced like other Expressions, so + # we shouldn't call builder.accept here. + builder.expression_depth += 1 + builder.reassigned_in_expr = find_walrus_targets(stmt.expr) + builder.expr_has_suspend = expr_has_suspend(stmt.expr) stmt.expr.accept(builder.visitor) - builder.flush_keep_alives(stmt.line) + builder.expression_depth -= 1 + builder.reassigned_in_expr = set() + builder.expr_has_suspend = False + builder.flush_keep_alives(stmt.line, scope=KEEP_ALIVE_SHORT_LIVED) + builder.flush_keep_alives(stmt.line, scope=KEEP_ALIVE_WHOLE_EXPRESSION) def transform_return_stmt(builder: IRBuilder, stmt: ReturnStmt) -> None: @@ -1108,6 +1123,10 @@ def transform_with( al = "a" if is_async else "" mgr_v = builder.accept(expr) + + if not is_async and mgr_v.type == lock_rprimitive: + transform_with_lock(builder, mgr_v, target, body, line) + return is_native = isinstance(mgr_v.type, RInstance) if is_native: value = builder.add(MethodCall(mgr_v, f"__{al}enter__", args=[], line=line)) @@ -1179,6 +1198,32 @@ def finally_body() -> None: ) +def transform_with_lock( + builder: IRBuilder, mgr_v: Value, target: Lvalue | None, body: GenFunc, line: int +) -> None: + """Optimized 'with' for librt.threading.Lock. + + Generate a simple try/finally with direct acquire/release calls. + Lock.__exit__ never suppresses exceptions, so we don't need the + full PEP 343 try/except/finally machinery. + """ + # __enter__: acquire the lock + value = builder.primitive_op(lock_acquire_op, [mgr_v], line) + + mgr = builder.maybe_spill(mgr_v) + + def try_body() -> None: + if target: + builder.assign(builder.get_assignment_target(target), value, line) + body() + + def finally_body() -> None: + # __exit__: release the lock (ignoring exception info) + builder.primitive_op(lock_release_op, [builder.read(mgr, line)], line) + + transform_try_finally_stmt(builder, try_body, finally_body, line) + + def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None: # Generate separate logic for each expr in it, left to right def generate(i: int) -> None: diff --git a/mypyc/irbuild/util.py b/mypyc/irbuild/util.py index 5eda51a1a5dea..a6f793ccdc1a1 100644 --- a/mypyc/irbuild/util.py +++ b/mypyc/irbuild/util.py @@ -12,6 +12,7 @@ ARG_POS, GDEF, ArgKind, + AssignmentStmt, BytesExpr, CallExpr, ClassDef, @@ -24,13 +25,17 @@ OverloadedFuncDef, RefExpr, StrExpr, + TempNode, TupleExpr, UnaryExpr, Var, + is_class_var, ) from mypy.semanal import refers_to_fullname from mypy.types import FINAL_DECORATOR_NAMES from mypyc.errors import Errors +from mypyc.ir.class_ir import ClassIR +from mypyc.ir.rtypes import is_none_rprimitive, is_object_rprimitive, is_optional_type MYPYC_ATTRS: Final[frozenset[MypycAttr]] = frozenset( ["native_class", "allow_interpreted_subclasses", "serializable", "free_list_len", "acyclic"] @@ -102,6 +107,50 @@ def dataclass_type(cdef: ClassDef) -> str | None: return None +def _defaults_skip(stmt: AssignmentStmt, cls_type: str | None) -> bool: + """Whether a class-level default assignment is skipped when emitting + __mypyc_defaults_setup, based on class type. + + - attr (auto_attribs=False): skip all (handled by attr.ib machinery). + - dataclasses / attr-auto: skip annotated assignments. + - regular extension class: skip nothing. + """ + if cls_type == "attr": + return True + if cls_type in ("dataclasses", "attr-auto"): + return stmt.type is not None + return False + + +def default_attr_name(stmt: AssignmentStmt, ir: ClassIR, cls_type: str | None) -> str | None: + """Return the attribute name if `stmt` is a class-level default assignment + that __mypyc_defaults_setup should emit; otherwise None. + + Single source of truth for the predicate used by both + mypyc.irbuild.classdef.find_attr_initializers (IR build) and + mypyc.irbuild.prepare._has_own_default_attrs (prepare-phase decl registration). + """ + lvalue = stmt.lvalues[0] + if not isinstance(lvalue, NameExpr) or is_class_var(lvalue): + return None + if isinstance(stmt.rvalue, TempNode): + return None + name = lvalue.name + if name in ("__slots__", "__deletable__") or name not in ir.attributes: + return None + if _defaults_skip(stmt, cls_type): + return None + if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == "builtins.None": + attr_type = ir.attributes[name] + if ( + not is_optional_type(attr_type) + and not is_object_rprimitive(attr_type) + and not is_none_rprimitive(attr_type) + ): + return None + return name + + def get_mypyc_attr_literal(e: Expression) -> Any: """Convert an expression from a mypyc_attr decorator to a value. diff --git a/mypyc/irbuild/vec.py b/mypyc/irbuild/vec.py index bfcfabee45c21..8dffc12a22130 100644 --- a/mypyc/irbuild/vec.py +++ b/mypyc/irbuild/vec.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from mypyc.common import IS_32_BIT_PLATFORM, PLATFORM_SIZE +from mypyc.common import IS_32_BIT_PLATFORM, KEEP_ALIVE_SHORT_LIVED, PLATFORM_SIZE from mypyc.ir.ops import ( ERR_MAGIC, Assign, @@ -52,6 +52,7 @@ vec_api_by_item_type, vec_item_type_tags, ) +from mypyc.primitives.librt_vecs_ops import vec_get_item_unsafe_borrow_op, vec_get_item_unsafe_op if TYPE_CHECKING: from mypyc.irbuild.ll_builder import LowLevelIRBuilder @@ -316,9 +317,10 @@ def vec_get_item( ) -> Value: """Generate inlined vec __getitem__ call. - We inline this, since it's simple but performance-critical. + We inline the length and bounds check, since they are simple but + performance-critical. The actual item load is emitted as a generic primitive + op that is lowered later. """ - # TODO: Support more item types # TODO: Support more index types len_val = vec_len(builder, base) index = vec_check_and_adjust_index(builder, len_val, index, line) @@ -328,13 +330,28 @@ def vec_get_item( def vec_get_item_unsafe( builder: LowLevelIRBuilder, base: Value, index: Value, line: int, *, can_borrow: bool = False ) -> Value: - """Get vec item, assuming index is non-negative and within bounds.""" + """Get vec item, assuming index is non-negative and within bounds. + + This emits a generic primitive op that is inlined during lowering. + """ + assert isinstance(base.type, RVec) + if can_borrow: + desc = vec_get_item_unsafe_borrow_op + else: + desc = vec_get_item_unsafe_op + return builder.primitive_op(desc, [base, index], line, type_args=[base.type.item_type]) + + +def vec_get_item_unsafe_lower( + builder: LowLevelIRBuilder, base: Value, index: Value, line: int, *, can_borrow: bool = False +) -> Value: + """Generate the low-level IR for an unsafe vec item load.""" assert isinstance(base.type, RVec) index = as_platform_int(builder, index, line) vtype = base.type item_addr = vec_item_ptr(builder, base, index) result = vec_load_mem_item(builder, item_addr, vtype.item_type, can_borrow=can_borrow) - builder.keep_alives.append(base) + builder.add_keep_alive(base, KEEP_ALIVE_SHORT_LIVED) return result diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 89ef4d0749a45..458db90efd530 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -3,8 +3,8 @@ #ifndef CPY_CPY_H #define CPY_CPY_H -#include #include +#include #include #include #include @@ -664,16 +664,68 @@ PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end); // List operations -PyObject *CPyList_Build(Py_ssize_t len, ...); +#ifndef Py_GIL_DISABLED + PyObject *CPyList_GetItem(PyObject *list, CPyTagged index); PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index); +PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index); +bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value); +bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value); + +#else + +PyObject *CPyList_GetItem_(PyObject *list, CPyTagged index); +bool CPyList_SetItem_(PyObject *list, CPyTagged index, PyObject *value); + +static inline PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) { + if (likely(CPyTagged_CheckShort(index) && !CPyTagged_IsNegative(index))) { + // Inlined fast path + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + return PyList_GetItemRef(list, n); + } else { + return CPyList_GetItem_(list, index); + } +} + +static inline PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) { + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + if (n < 0) { + n += PyList_GET_SIZE(list); + } + return PyList_GetItemRef(list, n); +} + +static inline PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index) { + if (index < 0) { + index += PyList_GET_SIZE(list); + } + return PyList_GetItemRef(list, index); +} + +static inline bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) { + if (likely(CPyTagged_CheckShort(index) && !CPyTagged_IsNegative(index))) { + // Inlined fast path + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + return PyList_SetItem(list, n, value) >= 0; + } else { + return CPyList_SetItem_(list, index, value); + } +} + +static inline bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value) { + if (index < 0) { + index += PyList_GET_SIZE(list); + } + return PyList_SetItem(list, index, value) >= 0; +} + +#endif + PyObject *CPyList_GetItemBorrow(PyObject *list, CPyTagged index); PyObject *CPyList_GetItemShortBorrow(PyObject *list, CPyTagged index); -PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index); PyObject *CPyList_GetItemInt64Borrow(PyObject *list, int64_t index); -bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value); void CPyList_SetItemUnsafe(PyObject *list, Py_ssize_t index, PyObject *value); -bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value); +PyObject *CPyList_Build(Py_ssize_t len, ...); PyObject *CPyList_PopLast(PyObject *obj); PyObject *CPyList_Pop(PyObject *obj, CPyTagged index); CPyTagged CPyList_Count(PyObject *obj, PyObject *value); diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index dc715600653d5..4aec322a730cd 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -1,9 +1,9 @@ #ifndef BYTESWRITER_EXTRA_OPS_H #define BYTESWRITER_EXTRA_OPS_H +#include #include #include -#include #include "mypyc_util.h" #include "strings/librt_strings_api.h" diff --git a/mypyc/lib-rt/dict_ops.c b/mypyc/lib-rt/dict_ops.c index dd93acdbe9267..8a9202c403f8a 100644 --- a/mypyc/lib-rt/dict_ops.c +++ b/mypyc/lib-rt/dict_ops.c @@ -15,13 +15,10 @@ // some indirections. PyObject *CPyDict_GetItem(PyObject *dict, PyObject *key) { if (PyDict_CheckExact(dict)) { - PyObject *res = PyDict_GetItemWithError(dict, key); - if (!res) { - if (!PyErr_Occurred()) { - PyErr_SetObject(PyExc_KeyError, key); - } - } else { - Py_INCREF(res); + PyObject *res; + int found = PyDict_GetItemRef(dict, key, &res); + if (found == 0) { + PyErr_SetObject(PyExc_KeyError, key); } return res; } else { @@ -56,14 +53,15 @@ PyObject *CPyDict_Build(Py_ssize_t size, ...) { PyObject *CPyDict_Get(PyObject *dict, PyObject *key, PyObject *fallback) { // We are dodgily assuming that get on a subclass doesn't have // different behavior. - PyObject *res = PyDict_GetItemWithError(dict, key); - if (!res) { - if (PyErr_Occurred()) { - return NULL; - } - res = fallback; + PyObject *res; + int found = PyDict_GetItemRef(dict, key, &res); + if (found < 0) { + return NULL; + } + if (found == 0) { + Py_INCREF(fallback); + return fallback; } - Py_INCREF(res); return res; } @@ -100,17 +98,19 @@ PyObject *CPyDict_SetDefaultWithEmptyDatatype(PyObject *dict, PyObject *key, } else if (data_type == 3) { new_obj = PySet_New(NULL); } else { - return NULL; + new_obj = NULL; } - if (CPyDict_SetItem(dict, key, new_obj) == -1) { - return NULL; + if (new_obj == NULL) { + res = NULL; + } else if (CPyDict_SetItem(dict, key, new_obj) == -1) { + Py_DECREF(new_obj); + res = NULL; } else { - return new_obj; + res = new_obj; } - } else { - return res; } + return res; } int CPyDict_SetItem(PyObject *dict, PyObject *key, PyObject *value) { @@ -346,16 +346,24 @@ tuple_T3CIO CPyDict_NextKey(PyObject *dict_or_iter, CPyTagged offset) { PyObject *dummy; if (PyDict_CheckExact(dict_or_iter)) { - ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &dummy); + PyObject *key; + // PyDict_Next() returns a borrowed reference. On free-threaded builds, + // hold the dict lock until we have converted it to a strong reference. + Py_BEGIN_CRITICAL_SECTION(dict_or_iter); + ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &key, &dummy); + if (ret.f0) { + ret.f2 = Py_NewRef(key); + } + Py_END_CRITICAL_SECTION(); + if (ret.f0) { ret.f1 = CPyTagged_FromSsize_t(py_offset); } else { // Set key to None, so mypyc can manage refcounts. ret.f1 = 0; ret.f2 = Py_None; + Py_INCREF(ret.f2); } - // PyDict_Next() returns borrowed references. - Py_INCREF(ret.f2); } else { // offset is dummy in this case, just use the old value. ret.f1 = offset; @@ -370,16 +378,24 @@ tuple_T3CIO CPyDict_NextValue(PyObject *dict_or_iter, CPyTagged offset) { PyObject *dummy; if (PyDict_CheckExact(dict_or_iter)) { - ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &dummy, &ret.f2); + PyObject *value; + // PyDict_Next() returns a borrowed reference. On free-threaded builds, + // hold the dict lock until we have converted it to a strong reference. + Py_BEGIN_CRITICAL_SECTION(dict_or_iter); + ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &dummy, &value); + if (ret.f0) { + ret.f2 = Py_NewRef(value); + } + Py_END_CRITICAL_SECTION(); + if (ret.f0) { ret.f1 = CPyTagged_FromSsize_t(py_offset); } else { // Set value to None, so mypyc can manage refcounts. ret.f1 = 0; ret.f2 = Py_None; + Py_INCREF(ret.f2); } - // PyDict_Next() returns borrowed references. - Py_INCREF(ret.f2); } else { // offset is dummy in this case, just use the old value. ret.f1 = offset; @@ -393,7 +409,18 @@ tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) { Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset); if (PyDict_CheckExact(dict_or_iter)) { - ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &ret.f3); + PyObject *key; + PyObject *value; + // PyDict_Next() returns borrowed references. On free-threaded builds, + // hold the dict lock until we have converted them to strong references. + Py_BEGIN_CRITICAL_SECTION(dict_or_iter); + ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &key, &value); + if (ret.f0) { + ret.f2 = Py_NewRef(key); + ret.f3 = Py_NewRef(value); + } + Py_END_CRITICAL_SECTION(); + if (ret.f0) { ret.f1 = CPyTagged_FromSsize_t(py_offset); } else { @@ -401,6 +428,8 @@ tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) { ret.f1 = 0; ret.f2 = Py_None; ret.f3 = Py_None; + Py_INCREF(ret.f2); + Py_INCREF(ret.f3); } } else { ret.f1 = offset; @@ -413,6 +442,8 @@ tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) { ret.f0 = 0; ret.f2 = Py_None; ret.f3 = Py_None; + Py_INCREF(ret.f2); + Py_INCREF(ret.f3); } else { ret.f0 = 1; ret.f2 = PyTuple_GET_ITEM(item, 0); @@ -423,9 +454,6 @@ tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) { return ret; } } - // PyDict_Next() returns borrowed references. - Py_INCREF(ret.f2); - Py_INCREF(ret.f3); return ret; } diff --git a/mypyc/lib-rt/function_wrapper.c b/mypyc/lib-rt/function_wrapper.c index ccb1824d24b4a..dfea6aed1d1c3 100644 --- a/mypyc/lib-rt/function_wrapper.c +++ b/mypyc/lib-rt/function_wrapper.c @@ -1,6 +1,6 @@ #define PY_SSIZE_T_CLEAN -#include #include "CPy.h" +#include #define CPyFunction_weakreflist(f) (((PyCFunctionObject *)f)->m_weakreflist) #define CPyFunction_class(f) ((PyObject*) ((PyCMethodObject *) (f))->mm_class) @@ -29,7 +29,14 @@ static void CPyFunction_dealloc(CPyFunction *m) { } static PyObject* CPyFunction_repr(CPyFunction *op) { - return PyUnicode_FromFormat("", op->func_name, (void *)op); + // Use helper to get name for free threading safety. + PyObject *name = CPyFunction_get_name((PyObject *)op, NULL); + if (unlikely(name == NULL)) { + return NULL; + } + PyObject *result = PyUnicode_FromFormat("", name, (void *)op); + Py_DECREF(name); + return result; } static PyObject* CPyFunction_call(PyObject *func, PyObject *args, PyObject *kw) { @@ -59,13 +66,15 @@ static PyMemberDef CPyFunction_members[] = { PyObject* CPyFunction_get_name(PyObject *op, void *context) { (void)context; CPyFunction *func = (CPyFunction *)op; + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(op); if (unlikely(func->func_name == NULL)) { func->func_name = PyUnicode_InternFromString(((PyCFunctionObject *)func)->m_ml->ml_name); - if (unlikely(func->func_name == NULL)) - return NULL; } - Py_INCREF(func->func_name); - return func->func_name; + result = func->func_name; + Py_XINCREF(result); + Py_END_CRITICAL_SECTION(); + return result; } int CPyFunction_set_name(PyObject *op, PyObject *value, void *context) { @@ -77,8 +86,13 @@ int CPyFunction_set_name(PyObject *op, PyObject *value, void *context) { } Py_INCREF(value); - Py_XDECREF(func->func_name); + // Decref outside critical section, since it could run arbitrary code. + PyObject *old; + Py_BEGIN_CRITICAL_SECTION(op); + old = func->func_name; func->func_name = value; + Py_END_CRITICAL_SECTION(); + Py_XDECREF(old); return 0; } @@ -182,6 +196,7 @@ static PyObject* CPyFunction_Vectorcall(PyObject *func, PyObject *const *args, s } +// Steals ml, name, and code. Borrows module. static CPyFunction* CPyFunction_Init(CPyFunction *op, PyMethodDef *ml, PyObject* name, PyObject *module, PyObject* code, bool set_self) { PyCFunctionObject *cf = (PyCFunctionObject *)op; @@ -192,12 +207,10 @@ static CPyFunction* CPyFunction_Init(CPyFunction *op, PyMethodDef *ml, PyObject* Py_XINCREF(module); cf->m_module = module; - Py_INCREF(name); op->func_name = name; ((PyCMethodObject *)op)->mm_class = NULL; - Py_XINCREF(code); op->func_code = code; CPyFunction_func_vectorcall(op) = CPyFunction_Vectorcall; @@ -229,15 +242,34 @@ PyObject* CPyFunction_New(PyObject *module, const char *filename, const char *fu PyCFunction func, int func_flags, const char *func_doc, int first_line, int code_flags, bool has_self_arg) { PyMethodDef *method = NULL; - PyObject *code = NULL, *op = NULL; + PyObject *code = NULL, *name = NULL, *op = NULL; bool set_self = false; +#ifdef Py_GIL_DISABLED + // Double-checked locking: the common case (type already created) is a + // lock-free atomic load. Only the first-time initialization takes the + // mutex, which serializes concurrent creators. + if (!_Py_atomic_load_ptr_acquire(&CPyFunctionType)) { + static PyMutex type_init_mutex = {0}; + PyMutex_Lock(&type_init_mutex); + if (!CPyFunctionType) { + PyTypeObject *type = (PyTypeObject *)PyType_FromSpec(&CPyFunction_spec); + if (unlikely(!type)) { + PyMutex_Unlock(&type_init_mutex); + goto err; + } + _Py_atomic_store_ptr_release(&CPyFunctionType, type); + } + PyMutex_Unlock(&type_init_mutex); + } +#else if (!CPyFunctionType) { CPyFunctionType = (PyTypeObject *)PyType_FromSpec(&CPyFunction_spec); if (unlikely(!CPyFunctionType)) { goto err; } } +#endif method = CPyMethodDef_New(funcname, func, func_flags, func_doc); if (unlikely(!method)) { @@ -247,18 +279,24 @@ PyObject* CPyFunction_New(PyObject *module, const char *filename, const char *fu if (unlikely(!code)) { goto err; } + name = PyUnicode_FromString(funcname); + if (unlikely(!name)) { + goto err; + } // Set m_self inside the function wrapper only if the wrapped function has no self arg // to pass m_self as the self arg when the function is called. // When the function has a self arg, it will come in the args vector passed to the // vectorcall handler. set_self = !has_self_arg; - op = (PyObject *)CPyFunction_Init(PyObject_GC_New(CPyFunction, CPyFunctionType), - method, PyUnicode_FromString(funcname), module, - code, set_self); - if (unlikely(!op)) { + CPyFunction *raw = PyObject_GC_New(CPyFunction, CPyFunctionType); + if (unlikely(!raw)) { goto err; } + op = (PyObject *)CPyFunction_Init(raw, method, name, module, code, set_self); + method = NULL; + name = NULL; + code = NULL; PyObject_GC_Track(op); return op; @@ -267,5 +305,7 @@ PyObject* CPyFunction_New(PyObject *module, const char *filename, const char *fu if (method) { PyMem_Free(method); } + Py_XDECREF(name); + Py_XDECREF(code); return NULL; } diff --git a/mypyc/lib-rt/getargsfast.c b/mypyc/lib-rt/getargsfast.c index 590306a7c52c2..e62c533649162 100644 --- a/mypyc/lib-rt/getargsfast.c +++ b/mypyc/lib-rt/getargsfast.c @@ -18,7 +18,22 @@ #include #include "CPy.h" -#define PARSER_INITED(parser) ((parser)->kwtuple != NULL) +// The kwtuple field doubles as the "parser has been initialized" flag: it is +// written last (after all other parser fields) and read first. On free-threaded +// builds the fast paths read it without holding any lock, so the write must be a +// release store and the reads acquire loads. That way a thread that observes a +// non-NULL kwtuple is guaranteed to also see the fully-initialized min/max/etc. +// fields. On GIL builds these are plain accesses with no overhead. +#ifdef Py_GIL_DISABLED +#define PARSER_KWTUPLE(parser) _Py_atomic_load_ptr_acquire(&(parser)->kwtuple) +#define SET_PARSER_KWTUPLE(parser, value) \ + _Py_atomic_store_ptr_release(&(parser)->kwtuple, (value)) +#else +#define PARSER_KWTUPLE(parser) ((parser)->kwtuple) +#define SET_PARSER_KWTUPLE(parser, value) ((parser)->kwtuple = (value)) +#endif + +#define PARSER_INITED(parser) (PARSER_KWTUPLE(parser) != NULL) /* Forward */ static int @@ -115,19 +130,21 @@ CPyArg_ParseStackAndKeywordsSimple(PyObject *const *args, Py_ssize_t nargs, PyOb /* List of static parsers. */ static struct CPyArg_Parser *static_arg_parsers = NULL; +#ifdef Py_GIL_DISABLED +// Serializes one-time initialization of parsers and insertion into the +// static_arg_parsers list. Only contended the first time a given compiled +// function is called; once a parser is initialized the fast paths never lock. +static PyMutex static_arg_parsers_mutex; +#endif + static int -parser_init(CPyArg_Parser *parser) +parser_init_locked(CPyArg_Parser *parser) { const char * const *keywords; const char *format; int i, len, min, max, nkw; PyObject *kwtuple; - assert(parser->keywords != NULL); - if (PARSER_INITED(parser)) { - return 1; - } - keywords = parser->keywords; /* scan keywords and count the number of positional-only parameters */ for (i = 0; keywords[i] && !*keywords[i]; i++) { @@ -244,14 +261,53 @@ parser_init(CPyArg_Parser *parser) PyUnicode_InternInPlace(&str); PyTuple_SET_ITEM(kwtuple, i, str); } - parser->kwtuple = kwtuple; assert(parser->next == NULL); parser->next = static_arg_parsers; static_arg_parsers = parser; + + // Publish the parser last: storing kwtuple marks it as initialized, so all + // other fields (and the list insertion above) must already be in place. On + // free-threaded builds this is a release store paired with the acquire loads + // in PARSER_INITED/PARSER_KWTUPLE. + SET_PARSER_KWTUPLE(parser, kwtuple); return 1; } +// Cold path of parser_init: perform the one-time initialization. On +// free-threaded builds this is serialized so that only one thread builds the +// parser and inserts it into the static_arg_parsers list. +static CPy_NOINLINE int +parser_init_slow(CPyArg_Parser *parser) +{ +#ifdef Py_GIL_DISABLED + PyMutex_Lock(&static_arg_parsers_mutex); + // Re-check now that we hold the lock: another thread may have initialized + // the parser while we were waiting. + if (PARSER_INITED(parser)) { + PyMutex_Unlock(&static_arg_parsers_mutex); + return 1; + } + int retval = parser_init_locked(parser); + PyMutex_Unlock(&static_arg_parsers_mutex); + return retval; +#else + return parser_init_locked(parser); +#endif +} + +// Hot path: a parser is almost always already initialized, so keep the common +// case inline and branch out to parser_init_slow only on first use. +static inline int +parser_init(CPyArg_Parser *parser) +{ + assert(parser->keywords != NULL); + if (likely(PARSER_INITED(parser))) { + return 1; + } + return parser_init_slow(parser); +} + static PyObject* find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key) { diff --git a/mypyc/lib-rt/list_ops.c b/mypyc/lib-rt/list_ops.c index ae1bc0cb1e6ec..e8fd061ada687 100644 --- a/mypyc/lib-rt/list_ops.c +++ b/mypyc/lib-rt/list_ops.c @@ -49,6 +49,8 @@ PyObject *CPyList_Copy(PyObject *list) { return PyObject_CallMethodNoArgs(list, mypyc_interned_str.copy); } +#ifndef Py_GIL_DISABLED + PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); Py_ssize_t size = PyList_GET_SIZE(list); @@ -69,24 +71,6 @@ PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) { return result; } -PyObject *CPyList_GetItemShortBorrow(PyObject *list, CPyTagged index) { - Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); - Py_ssize_t size = PyList_GET_SIZE(list); - if (n >= 0) { - if (n >= size) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - } else { - n += size; - if (n < 0) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - } - return PyList_GET_ITEM(list, n); -} - PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) { if (CPyTagged_CheckShort(index)) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); @@ -112,29 +96,6 @@ PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) { } } -PyObject *CPyList_GetItemBorrow(PyObject *list, CPyTagged index) { - if (CPyTagged_CheckShort(index)) { - Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); - Py_ssize_t size = PyList_GET_SIZE(list); - if (n >= 0) { - if (n >= size) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - } else { - n += size; - if (n < 0) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - } - return PyList_GET_ITEM(list, n); - } else { - PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG); - return NULL; - } -} - PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index) { size_t size = PyList_GET_SIZE(list); if (likely((uint64_t)index < size)) { @@ -156,23 +117,6 @@ PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index) { return result; } -PyObject *CPyList_GetItemInt64Borrow(PyObject *list, int64_t index) { - size_t size = PyList_GET_SIZE(list); - if (likely((uint64_t)index < size)) { - return PyList_GET_ITEM(list, index); - } - if (index >= 0) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - index += size; - if (index < 0) { - PyErr_SetString(PyExc_IndexError, "list index out of range"); - return NULL; - } - return PyList_GET_ITEM(list, index); -} - bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) { if (CPyTagged_CheckShort(index)) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); @@ -220,6 +164,95 @@ bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value) { return true; } +#else /* Py_GIL_DISABLED */ + +PyObject *CPyList_GetItem_(PyObject *list, CPyTagged index) { + if (CPyTagged_CheckShort(index)) { + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + if (n < 0) { + n += PyList_GET_SIZE(list); + } + return PyList_GetItemRef(list, n); + } else { + PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG); + return NULL; + } +} + +bool CPyList_SetItem_(PyObject *list, CPyTagged index, PyObject *value) { + if (CPyTagged_CheckShort(index)) { + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + Py_ssize_t size = PyList_GET_SIZE(list); + if (n < 0) { + n += size; + } + return PyList_SetItem(list, n, value) >= 0; + } else { + PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG); + return false; + } +} + +#endif + +PyObject *CPyList_GetItemShortBorrow(PyObject *list, CPyTagged index) { + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + Py_ssize_t size = PyList_GET_SIZE(list); + if (n >= 0) { + if (n >= size) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + } else { + n += size; + if (n < 0) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + } + return PyList_GET_ITEM(list, n); +} + +PyObject *CPyList_GetItemBorrow(PyObject *list, CPyTagged index) { + if (CPyTagged_CheckShort(index)) { + Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); + Py_ssize_t size = PyList_GET_SIZE(list); + if (n >= 0) { + if (n >= size) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + } else { + n += size; + if (n < 0) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + } + return PyList_GET_ITEM(list, n); + } else { + PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG); + return NULL; + } +} + +PyObject *CPyList_GetItemInt64Borrow(PyObject *list, int64_t index) { + size_t size = PyList_GET_SIZE(list); + if (likely((uint64_t)index < size)) { + return PyList_GET_ITEM(list, index); + } + if (index >= 0) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + index += size; + if (index < 0) { + PyErr_SetString(PyExc_IndexError, "list index out of range"); + return NULL; + } + return PyList_GET_ITEM(list, index); +} + // This function should only be used to fill in brand new lists. void CPyList_SetItemUnsafe(PyObject *list, Py_ssize_t index, PyObject *value) { PyList_SET_ITEM(list, index, value); @@ -301,6 +334,8 @@ PyObject *CPyList_Extend(PyObject *o1, PyObject *o2) { } // Return -2 or error, -1 if not found, or index of first match otherwise. +// +// The caller must hold a critical section on the list. static Py_ssize_t _CPyList_Find(PyObject *list, PyObject *obj) { Py_ssize_t i; for (i = 0; i < Py_SIZE(list); i++) { @@ -320,27 +355,35 @@ static Py_ssize_t _CPyList_Find(PyObject *list, PyObject *obj) { } int CPyList_Remove(PyObject *list, PyObject *obj) { + int retval; + Py_BEGIN_CRITICAL_SECTION(list); Py_ssize_t index = _CPyList_Find(list, obj); if (index == -2) { - return -1; - } - if (index == -1) { + retval = -1; + } else if (index == -1) { PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); - return -1; + retval = -1; + } else { + retval = PyList_SetSlice(list, index, index + 1, NULL); } - return PyList_SetSlice(list, index, index + 1, NULL); + Py_END_CRITICAL_SECTION(); + return retval; } CPyTagged CPyList_Index(PyObject *list, PyObject *obj) { + CPyTagged retval; + Py_BEGIN_CRITICAL_SECTION(list); Py_ssize_t index = _CPyList_Find(list, obj); if (index == -2) { - return CPY_INT_TAG; - } - if (index == -1) { + retval = CPY_INT_TAG; + } else if (index == -1) { PyErr_SetString(PyExc_ValueError, "value is not in list"); - return CPY_INT_TAG; + retval = CPY_INT_TAG; + } else { + retval = index << 1; } - return index << 1; + Py_END_CRITICAL_SECTION(); + return retval; } PyObject *CPySequence_Sort(PyObject *seq) { diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 2aaadb2ac47d2..392dba0deca4c 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -1281,12 +1281,17 @@ static int CPyImport_SetModuleFile(PyObject *modobj, PyObject *module_name, Py_DECREF(file); return 0; } - // Derive __file__ from the shared library's __file__ (for its - // directory), the module name (dots -> path separators), and the - // extension suffix. E.g. for module "a.b.c", shared lib - // "/path/to/group__mypyc.cpython-312-x86_64-linux-gnu.so", - // suffix ".cpython-312-x86_64-linux-gnu.so": - // => "/path/to/a/b/c.cpython-312-x86_64-linux-gnu.so" + // Derive __file__ from the shared lib's directory, the module + // name, and the extension suffix. Two layouts: + // + // Monolithic: one shared lib above the package tree holds many + // modules, so append the full dotted module path. + // separate=True: each module has its own "__mypyc.so" + // next to the module, so dirname(shared_lib) is already inside + // the parent package. Append only the last segment. + // + // Detect the separate=True case by matching the shared lib's + // basename against "__mypyc". PyObject *derived_file = NULL; if (shared_lib_file != NULL && shared_lib_file != Py_None && PyUnicode_Check(shared_lib_file)) { @@ -1314,30 +1319,65 @@ static int CPyImport_SetModuleFile(PyObject *modobj, PyObject *module_name, if (module_path == NULL) { return -1; } + + // Compute the module's last dotted segment for the separate=True check. + Py_ssize_t name_len = PyUnicode_GetLength(module_name); + Py_ssize_t last_dot = PyUnicode_FindChar(module_name, '.', 0, name_len, -1); + PyObject *last_segment; + if (last_dot >= 0) { + last_segment = PyUnicode_Substring(module_name, last_dot + 1, name_len); + } else { + last_segment = module_name; + Py_INCREF(last_segment); + } + if (last_segment == NULL) { + Py_DECREF(module_path); + return -1; + } + // Compare shared_lib_file basename against "__mypyc". + PyObject *expected_basename = PyUnicode_FromFormat( + "%U__mypyc%U", last_segment, ext_suffix); + PyObject *actual_basename; + if (sep >= 0) { + actual_basename = PyUnicode_Substring(shared_lib_file, sep + 1, sf_len); + } else { + actual_basename = shared_lib_file; + Py_INCREF(actual_basename); + } + int is_per_module_lib = 0; + if (expected_basename != NULL && actual_basename != NULL) { + is_per_module_lib = + (PyUnicode_Compare(expected_basename, actual_basename) == 0); + } + Py_XDECREF(expected_basename); + Py_XDECREF(actual_basename); + // For packages, __file__ should point to __init__, // e.g. "a/b/__init__.cpython-312-x86_64-linux-gnu.so". + PyObject *file_path = is_per_module_lib ? last_segment : module_path; if (sep >= 0) { PyObject *dir = PyUnicode_Substring(shared_lib_file, 0, sep); if (dir != NULL) { if (is_package) { derived_file = PyUnicode_FromFormat( "%U%c%U%c__init__%U", dir, (int)sep_char, - module_path, (int)sep_char, ext_suffix); + file_path, (int)sep_char, ext_suffix); } else { derived_file = PyUnicode_FromFormat( "%U%c%U%U", dir, (int)sep_char, - module_path, ext_suffix); + file_path, ext_suffix); } Py_DECREF(dir); } } else { if (is_package) { derived_file = PyUnicode_FromFormat( - "%U%c__init__%U", module_path, (int)SEP[0], ext_suffix); + "%U%c__init__%U", file_path, (int)SEP[0], ext_suffix); } else { - derived_file = PyUnicode_FromFormat("%U%U", module_path, ext_suffix); + derived_file = PyUnicode_FromFormat("%U%U", file_path, ext_suffix); } } + Py_DECREF(last_segment); Py_DECREF(module_path); } if (derived_file == NULL && !PyErr_Occurred()) { diff --git a/mypyc/lib-rt/mypyc_util.h b/mypyc/lib-rt/mypyc_util.h index 4a4552d059b8c..d7a3eb3214bed 100644 --- a/mypyc/lib-rt/mypyc_util.h +++ b/mypyc/lib-rt/mypyc_util.h @@ -156,6 +156,10 @@ static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) { return x << 1; } +static inline int CPyTagged_IsNegative(CPyTagged x) { + return ((Py_ssize_t)x) < 0; +} + // Are we targeting Python 3.X or newer? #define CPY_3_11_FEATURES (PY_VERSION_HEX >= 0x030b0000) #define CPY_3_12_FEATURES (PY_VERSION_HEX >= 0x030c0000) diff --git a/mypyc/lib-rt/pythonsupport.c b/mypyc/lib-rt/pythonsupport.c index 0a99f0ae2e29b..a8f5a4f4ad4ea 100644 --- a/mypyc/lib-rt/pythonsupport.c +++ b/mypyc/lib-rt/pythonsupport.c @@ -5,6 +5,27 @@ #include "pythonsupport.h" +#ifdef Py_GIL_DISABLED +// Cold slow path of CPy_GetAttrRef (declared in pythonsupport.h). Reached only +// when the inline fast-path try-incref fails: the value is owned by another +// thread, so taking a reference requires an atomic shared-refcount operation. +// Kept out-of-line so the inline fast path stays small. +// +// First try the lock-free shared-refcount CAS. If the value has not had +// maybe-weakref set yet (for example, it was published by CPy_InitAttrRef), force +// a cross-thread reference via _Py_NewRefWithLock, which cannot fail and sets +// maybe-weakref so subsequent reads take the fast path. The value was already +// observed in the field by CPy_GetAttrRef; CPy_SetAttrRef's QSBR-delayed decref +// keeps any replaced value alive long enough for this reader. +CPy_NOINLINE +PyObject *CPy_GetAttrRefSlow(PyObject *v) { + if (_Py_TryIncRefShared(v)) { + return v; + } + return _Py_NewRefWithLock(v); // sets maybe-weakref; cannot fail +} +#endif + ///////////////////////////////////////// // Adapted from bltinmodule.c in Python 3.7.0 PyObject* diff --git a/mypyc/lib-rt/pythonsupport.h b/mypyc/lib-rt/pythonsupport.h index 4c82ff6a3c037..33c5a596d1747 100644 --- a/mypyc/lib-rt/pythonsupport.h +++ b/mypyc/lib-rt/pythonsupport.h @@ -6,8 +6,8 @@ #ifndef CPY_PYTHONSUPPORT_H #define CPY_PYTHONSUPPORT_H -#include #include +#include #include "pythoncapi_compat.h" #include #include @@ -23,6 +23,10 @@ #include "internal/pycore_setobject.h" // _PySet_Update #endif +#ifdef Py_GIL_DISABLED +#include "internal/pycore_object.h" // _Py_TryIncrefFast, _Py_TryIncRefShared +#endif + #if CPY_3_12_FEATURES #include "internal/pycore_frame.h" #endif @@ -34,6 +38,136 @@ extern "C" { } // why isn't emacs smart enough to not indent this #endif +#ifdef Py_GIL_DISABLED +// Read a native attribute that is a single reference-counted 'PyObject *' field, +// returning a new reference (or NULL if the field is NULL/undefined). +// +// On free-threaded builds a plain load followed by an incref races with a +// concurrent setter that may decref the old value to zero and free it before the +// incref runs (use-after-free). CPy_SetAttrRef avoids that by reclaiming old +// values through QSBR-delayed decref, so a value observed in the field remains +// safe to touch while this reader is running. +// +// Only the hot case is inlined here: an incref of a value owned by this thread or +// immortal, via '_Py_TryIncrefFast' (no CAS, no loop). Everything colder -- the +// cross-thread shared-refcount CAS and the _Py_NewRefWithLock fallback -- lives +// out-of-line in 'CPy_GetAttrRefSlow'. Splitting it this way keeps each call +// site's fast path small enough to inline, which is measurably faster than +// letting the compiler auto-out-line the whole helper (that merges every read +// site's branch history into one shared copy and mispredicts). It is only used in +// free-threaded builds; the default (GIL) build keeps the plain load + incref +// generated inline by mypyc. +PyObject *CPy_GetAttrRefSlow(PyObject *v); + +static inline PyObject *CPy_GetAttrRef(PyObject **field) { + PyObject *v = (PyObject *)_Py_atomic_load_ptr_acquire(field); + if (v == NULL) { + return NULL; + } + if (_Py_TryIncrefFast(v)) { + return v; + } + return CPy_GetAttrRefSlow(v); +} + +// Read a native attribute that is a single reference-counted 'PyObject *' field +// AND is Final (assigned once during construction, never rebound -- mypyc emits no +// setter for it), returning a new reference (or NULL if undefined). +// +// A Final attribute has no concurrent writer after 'self' is published, so the +// use-after-free race that CPy_GetAttrRef guards against cannot happen: the field +// holds a strong reference for the object's whole lifetime, and any thread reading +// it necessarily holds 'self', which keeps the value alive. So the try-incref + +// _Py_NewRefWithLock fallback are unnecessary here -- a plain load + Py_INCREF is +// safe. A cross-thread Py_INCREF is an unconditional +// atomic add on ob_ref_shared, so (unlike CPy_GetAttrRef's try-incref) it needs no +// maybe-weakref and has no slow path. The load is relaxed rather than acquire: the +// reader reached 'self' through a synchronization edge (self's own publication) +// that already ordered the construction stores before it, exactly as with +// CPy_InitAttrRef's relaxed store. Relaxed keeps it TSan-clean at zero cost (plain +// mov/ldr). +static inline PyObject *CPy_GetAttrRefFinal(PyObject **field) { + PyObject *v = (PyObject *)_Py_atomic_load_ptr_relaxed(field); + if (v != NULL) { + Py_INCREF(v); + } + return v; +} + +// Reclaim the previous value of a native attribute after it has been replaced. +// +// CPy_GetAttrRef reads the field optimistically without holding any lock the +// writer also takes, so a reader can load the old pointer and then try to take a +// reference after this store. The old value must therefore stay alive until every +// thread has passed a quiescent point, which is exactly what a QSBR-deferred +// decref guarantees. So all mortal old values are +// reclaimed via _PyObject_XDecRefDelayed, matching CPython's own replace-a-slot +// paths (e.g. _PyObject_SetDict / _PyObject_SetManagedDict). +// +// We deliberately do NOT take a "local refcount > 1, owned by this thread" fast +// path: dropping the field's reference is not the only decref of the object, so a +// non-freeing local decrement here does not prevent an unrelated reference holder +// from driving the object to zero (a plain, non-deferred Py_DECREF -> _Py_Dealloc) +// while an in-flight reader still holds the stale pointer -- a use-after-free that +// only QSBR deferral closes. Immortal objects are never freed, so skipping their +// decref entirely is safe and avoids queuing a no-op onto the delayed-free list. +static inline void CPy_DecRefAttrOld(PyObject *op) { + if (op == NULL) { + return; + } + if (_Py_IsImmortal(op)) { + return; + } + _PyObject_XDecRefDelayed(op); +} + +// Set a native attribute that is a single reference-counted 'PyObject *' field, +// stealing the reference to 'value' (which may be NULL to delete the attribute) +// and safely reclaiming the previous value. +// +// Memory safety does NOT depend on SetMaybeWeakref here, so (unlike an earlier +// version) we do not call it. Two things keep this safe: +// - The old value is reclaimed via CPy_DecRefAttrOld, a QSBR-deferred decref, so +// it cannot be freed while an in-flight CPy_GetAttrRef still holds the stale +// pointer. +// - A concurrent cross-thread reader whose inline fast-path try-incref fails on +// an unflagged 'value' still cannot fail and never blocks on this writer: +// CPy_GetAttrRef falls into CPy_GetAttrRefSlow, which is fully lock-free. It +// retries with a lock-free shared-refcount CAS (_Py_TryIncRefShared) and, only +// if that also fails, forces a reference via _Py_NewRefWithLock, which cannot +// fail and lazily sets maybe-weakref so later cross-thread reads take the CAS. +// This mirrors CPy_InitAttrRef, which already omits SetMaybeWeakref for the same +// reason. Setting the flag here would only be a possible performance tuning knob +// (it would let that first cross-thread reader succeed on the cheaper +// _Py_TryIncRefShared CAS instead of falling through to _Py_NewRefWithLock); it is +// not needed for correctness. The atomic exchange publishes the new pointer and +// hands back the old one without a writer/writer race. +static inline void CPy_SetAttrRef(PyObject **field, PyObject *value) { + PyObject *old = (PyObject *)_Py_atomic_exchange_ptr(field, value); + CPy_DecRefAttrOld(old); +} + +// Initialize a native attribute that is known to be previously undefined (NULL), +// stealing the reference to 'value'. +// +// Initializer stores only happen while 'self' is still thread-local (the +// attribute-definedness analysis marks a SetAttr as an initializer only before +// 'self' can leak -- see mypyc/analysis/attrdefined.py). So there is no old value +// to reclaim, no competing writer, and the field store is not itself the +// publication point: 'self' is published later (when it escapes __init__ or is +// returned), and that publication carries the release barrier making all the +// construction stores visible. A relaxed store therefore suffices. +// +// Unlike CPy_SetAttrRef, this deliberately does NOT call SetMaybeWeakref (its CAS +// is pure overhead here, ~+2.6ns per fresh store, and construction-heavy code +// pays it on every attribute of every new object). The cost is moved off this hot +// path onto CPy_GetAttrRef's cold slow path, which sets maybe-weakref lazily on +// the first cross-thread read that needs it. +static inline void CPy_InitAttrRef(PyObject **field, PyObject *value) { + _Py_atomic_store_ptr_relaxed(field, value); +} +#endif + PyObject* update_bases(PyObject *bases); int init_subclass(PyTypeObject *type, PyObject *kwds); @@ -205,6 +339,25 @@ list_count(PyListObject *self, PyObject *value) Py_ssize_t count = 0; Py_ssize_t i; +#ifdef Py_GIL_DISABLED + for (i = 0; i < PyList_GET_SIZE(self); i++) { + PyObject *item = PyList_GetItemRef((PyObject *)self, i); + if (unlikely(item == NULL)) { + // Race condition: list shrank between size read and get item + if (PyErr_ExceptionMatches(PyExc_IndexError)) { + PyErr_Clear(); + break; + } + return CPY_INT_TAG; + } + int cmp = PyObject_RichCompareBool(item, value, Py_EQ); + Py_DECREF(item); + if (cmp > 0) + count++; + else if (cmp < 0) + return CPY_INT_TAG; + } +#else for (i = 0; i < Py_SIZE(self); i++) { int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ); if (cmp > 0) @@ -212,6 +365,7 @@ list_count(PyListObject *self, PyObject *value) else if (cmp < 0) return CPY_INT_TAG; } +#endif return CPyTagged_ShortFromSsize_t(count); } diff --git a/mypyc/lib-rt/random/librt_random.c b/mypyc/lib-rt/random/librt_random.c new file mode 100644 index 0000000000000..7dc590eaa5946 --- /dev/null +++ b/mypyc/lib-rt/random/librt_random.c @@ -0,0 +1,762 @@ +#include "pythoncapi_compat.h" + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "mypyc_util.h" +#include "CPy.h" +#include "librt_random.h" + +// +// ChaCha8 PRNG with forward secrecy +// + +#define CHACHA8_RESEED_INTERVAL 16 + +typedef struct { + uint32_t seed[8]; // 256-bit key + uint32_t buf[16]; // output buffer: one ChaCha8 block + uint32_t counter; // block counter + uint8_t used; // index into buf + uint8_t n; // usable values in buf (8 or 16) + uint8_t blocks_left; // blocks until next reseed +} chacha8_rng; + +static inline uint32_t +rotl32(uint32_t x, int n) { + return (x << n) | (x >> (32 - n)); +} + +#define QUARTERROUND(a, b, c, d) \ + do { \ + a += b; d ^= a; d = rotl32(d, 16); \ + c += d; b ^= c; b = rotl32(b, 12); \ + a += b; d ^= a; d = rotl32(d, 8); \ + c += d; b ^= c; b = rotl32(b, 7); \ + } while (0) + +static void +chacha8_block(const uint32_t seed[8], uint32_t counter, uint32_t out[16]) +{ + // "expand 32-byte k" + uint32_t s[16] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + seed[0], seed[1], seed[2], seed[3], + seed[4], seed[5], seed[6], seed[7], + counter, 0, 0, 0 // counter (low 32), counter (high 32), nonce + }; + + memcpy(out, s, sizeof(uint32_t) * 16); + + // 4 double-rounds = 8 rounds + for (int i = 0; i < 4; i++) { + // Column rounds + QUARTERROUND(out[0], out[4], out[ 8], out[12]); + QUARTERROUND(out[1], out[5], out[ 9], out[13]); + QUARTERROUND(out[2], out[6], out[10], out[14]); + QUARTERROUND(out[3], out[7], out[11], out[15]); + // Diagonal rounds + QUARTERROUND(out[0], out[5], out[10], out[15]); + QUARTERROUND(out[1], out[6], out[11], out[12]); + QUARTERROUND(out[2], out[7], out[ 8], out[13]); + QUARTERROUND(out[3], out[4], out[ 9], out[14]); + } + + // Add original state back (standard ChaCha finalization) + for (int i = 0; i < 16; i++) + out[i] += s[i]; +} + +// Fill entropy from OS via os.urandom(), which handles short reads, +// EINTR, and platform differences internally. +// Returns 0 on success, -1 on failure (with Python exception set). +static int +fill_os_entropy(void *buf, size_t len) +{ + PyObject *os_mod = PyImport_ImportModule("os"); + if (os_mod == NULL) + return -1; + PyObject *bytes = PyObject_CallMethod(os_mod, "urandom", "n", (Py_ssize_t)len); + Py_DECREF(os_mod); + if (bytes == NULL) + return -1; + memcpy(buf, PyBytes_AS_STRING(bytes), len); + Py_DECREF(bytes); + return 0; +} + +static void +chacha8_refill(chacha8_rng *rng) +{ + chacha8_block(rng->seed, rng->counter, rng->buf); + rng->counter++; + rng->used = 0; + rng->blocks_left--; + + if (unlikely(rng->blocks_left == 0)) { + // Forward secrecy reseed: steal last 8 words as new key + memcpy(rng->seed, rng->buf + 8, sizeof(uint32_t) * 8); + rng->n = 8; // only 8 words usable this block + rng->counter = 0; + rng->blocks_left = CHACHA8_RESEED_INTERVAL; + } else { + rng->n = 16; + } +} + +static inline uint32_t +chacha8_next(chacha8_rng *rng) +{ + if (unlikely(rng->used >= rng->n)) + chacha8_refill(rng); + return rng->buf[rng->used++]; +} + +// Return 64 bits of randomness (two consecutive 32-bit words, single bounds check). +static inline uint64_t +chacha8_next64(chacha8_rng *rng) +{ + // Need 2 words available; if fewer than 2, refill first. + if (unlikely(rng->used + 1 >= rng->n)) + // Use two separate calls to handle block boundary correctly. + return ((uint64_t)chacha8_next(rng) << 32) | chacha8_next(rng); + uint32_t hi = rng->buf[rng->used++]; + uint32_t lo = rng->buf[rng->used++]; + return ((uint64_t)hi << 32) | lo; +} + +// Return a uniformly distributed random value in [0, range). +// Use Lemire's nearly divisionless method for small ranges, and a portable +// rejection sampler for larger ranges to avoid non-standard 128-bit arithmetic. +static inline uint64_t +chacha8_next_ranged(chacha8_rng *rng, uint64_t range) +{ + assert(range != 0); + if (likely(range <= UINT32_MAX)) { + // 32-bit Lemire: multiply r * range to get 64-bit product, + // upper 32 bits are the result in [0, range). + uint64_t m = (uint64_t)chacha8_next(rng) * range; + uint32_t lo = (uint32_t)m; + if (unlikely(lo < range)) { + uint32_t thresh = (uint32_t)(-(uint32_t)range) % (uint32_t)range; + while (lo < thresh) { + m = (uint64_t)chacha8_next(rng) * range; + lo = (uint32_t)m; + } + } + return m >> 32; + } + // If range is a power of two, masking produces an unbiased result. + if ((range & (range - 1)) == 0) { + return chacha8_next64(rng) & (range - 1); + } + uint64_t r; + // In unsigned arithmetic, -range is 2**64 - range, so this computes + // 2**64 % range. Rejecting values below this threshold leaves exactly + // floor(2**64 / range) full buckets of size range, avoiding modulo bias. + uint64_t thresh = -range % range; + do { + r = chacha8_next64(rng); + } while (unlikely(r < thresh)); + return r % range; +} + +// Return a random i64 starting at 'start', with 'range' possible values. +// A zero range represents the full 2**64 i64 domain. +static inline int64_t +random_i64_from_range(chacha8_rng *rng, int64_t start, uint64_t range) +{ + uint64_t offset = range == 0 ? chacha8_next64(rng) : chacha8_next_ranged(rng, range); + return (int64_t)((uint64_t)start + offset); +} + +static void +chacha8_reset(chacha8_rng *rng) +{ + rng->counter = 0; + rng->used = 16; // force immediate refill on first call + rng->n = 16; + rng->blocks_left = CHACHA8_RESEED_INTERVAL; +} + +static int +chacha8_init(chacha8_rng *rng) +{ + if (fill_os_entropy(rng->seed, sizeof(rng->seed)) < 0) + return -1; + chacha8_reset(rng); + return 0; +} + +// Seed from an integer by hashing it through ChaCha8 to fill the 256-bit key. +static void +chacha8_seed_int(chacha8_rng *rng, int64_t seed_val) +{ + // Use the integer to construct a simple initial key, then run one + // ChaCha8 block to diffuse it across all 256 bits. + memset(rng->seed, 0, sizeof(rng->seed)); + rng->seed[0] = (uint32_t)(seed_val & 0xFFFFFFFF); + rng->seed[1] = (uint32_t)((uint64_t)seed_val >> 32); + + uint32_t out[16]; + chacha8_block(rng->seed, 0, out); + memcpy(rng->seed, out, sizeof(rng->seed)); + chacha8_reset(rng); +} + +// +// Thread-local global RNG for module-level random()/randint() +// +// thread_local pointer for fast access (direct %fs/%gs-relative load), +// platform TLS key with destructor for cleanup on thread exit. +// + +#ifdef _WIN32 +static __declspec(thread) chacha8_rng *tls_rng = NULL; +#else +static __thread chacha8_rng *tls_rng = NULL; +#endif + +#ifdef _WIN32 +static DWORD tls_key = FLS_OUT_OF_INDEXES; + +static void NTAPI +tls_rng_destructor(void *ptr) +{ + if (ptr != NULL) { + memset(ptr, 0, sizeof(chacha8_rng)); + PyMem_RawFree(ptr); + } +} +#else +static pthread_key_t tls_key; + +static void +tls_rng_destructor(void *ptr) +{ + if (ptr != NULL) { + memset(ptr, 0, sizeof(chacha8_rng)); + PyMem_RawFree(ptr); + } +} +#endif + +static int tls_key_created = 0; + +static int +ensure_tls_key(void) +{ + if (likely(tls_key_created)) + return 0; +#ifdef _WIN32 + tls_key = FlsAlloc(tls_rng_destructor); + if (tls_key == FLS_OUT_OF_INDEXES) { + PyErr_SetString(PyExc_OSError, "FlsAlloc failed"); + return -1; + } +#else + if (pthread_key_create(&tls_key, tls_rng_destructor) != 0) { + PyErr_SetString(PyExc_OSError, "pthread_key_create failed"); + return -1; + } +#endif + tls_key_created = 1; + return 0; +} + +// Get the thread-local RNG, initializing on first use. +// Returns NULL with Python exception set on failure. +static inline chacha8_rng * +get_thread_rng(void) +{ + chacha8_rng *rng = tls_rng; + if (likely(rng != NULL)) + return rng; + + // First use on this thread — allocate and seed + rng = PyMem_RawMalloc(sizeof(chacha8_rng)); + if (rng == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (chacha8_init(rng) < 0) { + PyMem_RawFree(rng); + return NULL; + } + + // Register with platform TLS for destructor +#ifdef _WIN32 + FlsSetValue(tls_key, rng); +#else + pthread_setspecific(tls_key, rng); +#endif + + tls_rng = rng; + return rng; +} + +// Return a random double in [0.0, 1.0) with 53 bits of mantissa precision. +static inline double +random_double_impl(chacha8_rng *rng) +{ + uint64_t r = chacha8_next64(rng); + return (double)(r >> 11) * (1.0 / 9007199254740992.0); // 1/2^53 +} + +// +// Module-level random() and randint() +// + +static PyObject* +module_random(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return NULL; + return PyFloat_FromDouble(random_double_impl(rng)); +} + +// Generate random integer in [a, b] using the given RNG. +static inline PyObject* +randint_impl(chacha8_rng *rng, int64_t a, int64_t b) +{ + uint64_t range = (uint64_t)b - (uint64_t)a + 1; + return PyLong_FromLongLong(random_i64_from_range(rng, a, range)); +} + +static PyObject* +module_randint(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, + "randint() takes exactly 2 arguments (%zd given)", nargs); + return NULL; + } + + int64_t a = CPyLong_AsInt64(args[0]); + if (unlikely(a == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + + int64_t b = CPyLong_AsInt64(args[1]); + if (unlikely(b == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + + if (a > b) { + PyErr_SetString(PyExc_ValueError, + "empty range for randint()"); + return NULL; + } + + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return NULL; + + return randint_impl(rng, a, b); +} + +// Parse 1 or 2 int args for randrange([start,] stop). +// Sets *a to start (default 0), *b to stop-1. +// Returns 0 on success, -1 on error (with exception set). +static int +parse_randrange_args(PyObject *const *args, Py_ssize_t nargs, + int64_t *a, int64_t *b) +{ + if (nargs == 1) { + *a = 0; + int64_t stop = CPyLong_AsInt64(args[0]); + if (unlikely(stop == CPY_LL_INT_ERROR && PyErr_Occurred())) + return -1; + if (stop <= 0) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return -1; + } + *b = stop - 1; + } else if (nargs == 2) { + *a = CPyLong_AsInt64(args[0]); + if (unlikely(*a == CPY_LL_INT_ERROR && PyErr_Occurred())) + return -1; + int64_t stop = CPyLong_AsInt64(args[1]); + if (unlikely(stop == CPY_LL_INT_ERROR && PyErr_Occurred())) + return -1; + if (*a >= stop) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return -1; + } + *b = stop - 1; + } else { + PyErr_Format(PyExc_TypeError, + "randrange() takes 1 or 2 arguments (%zd given)", nargs); + return -1; + } + return 0; +} + +static PyObject* +module_randrange(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + int64_t a, b; + if (parse_randrange_args(args, nargs, &a, &b) < 0) + return NULL; + + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return NULL; + + return randint_impl(rng, a, b); +} + +static PyObject* +module_seed(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + if (nargs != 1) { + PyErr_Format(PyExc_TypeError, + "seed() takes exactly 1 argument (%zd given)", nargs); + return NULL; + } + int64_t seed_val = CPyLong_AsInt64(args[0]); + if (unlikely(seed_val == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return NULL; + + chacha8_seed_int(rng, seed_val); + Py_RETURN_NONE; +} + +// +// Random Python type +// + +typedef struct { + PyObject_HEAD + chacha8_rng rng; +} RandomObject; + +static PyTypeObject RandomType; + +static PyObject* +Random_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + if (type != &RandomType) { + PyErr_SetString(PyExc_TypeError, "Random cannot be subclassed"); + return NULL; + } + + RandomObject *self = (RandomObject *)type->tp_alloc(type, 0); + // Seeding is done in tp_init + return (PyObject *)self; +} + +static int +Random_init(RandomObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *seed_obj = NULL; + + if (!PyArg_ParseTuple(args, "|O", &seed_obj)) { + return -1; + } + + if (kwds != NULL && PyDict_Size(kwds) > 0) { + PyErr_SetString(PyExc_TypeError, + "Random() takes no keyword arguments"); + return -1; + } + + if (seed_obj == NULL || seed_obj == Py_None) { + if (chacha8_init(&self->rng) < 0) + return -1; + } else { + int64_t seed_val = CPyLong_AsInt64(seed_obj); + if (unlikely(seed_val == CPY_LL_INT_ERROR && PyErr_Occurred())) + return -1; + chacha8_seed_int(&self->rng, seed_val); + } + + return 0; +} + +// Internal constructors for capsule API (bypass tp_new/tp_init) + +static PyObject * +Random_internal(void) { + RandomObject *self = (RandomObject *)RandomType.tp_alloc(&RandomType, 0); + if (self == NULL) + return NULL; + if (chacha8_init(&self->rng) < 0) { + Py_DECREF(self); + return NULL; + } + return (PyObject *)self; +} + +static PyObject * +Random_from_seed_internal(int64_t seed_val) { + RandomObject *self = (RandomObject *)RandomType.tp_alloc(&RandomType, 0); + if (self == NULL) + return NULL; + chacha8_seed_int(&self->rng, seed_val); + return (PyObject *)self; +} + +static PyTypeObject * +Random_type_internal(void) { + return &RandomType; +} + +static int64_t +Random_randrange1_internal(PyObject *self, int64_t stop) { + if (unlikely(stop <= 0)) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return CPY_LL_INT_ERROR; + } + return (int64_t)chacha8_next_ranged(&((RandomObject *)self)->rng, (uint64_t)stop); +} + +static int64_t +Random_randrange2_internal(PyObject *self, int64_t start, int64_t stop) { + if (unlikely(start >= stop)) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return CPY_LL_INT_ERROR; + } + uint64_t range = (uint64_t)stop - (uint64_t)start; + return random_i64_from_range(&((RandomObject *)self)->rng, start, range); +} + +static int64_t +Random_randint_internal(PyObject *self, int64_t a, int64_t b) { + if (unlikely(a > b)) { + PyErr_SetString(PyExc_ValueError, "empty range for randint()"); + return CPY_LL_INT_ERROR; + } + uint64_t range = (uint64_t)b - (uint64_t)a + 1; + return random_i64_from_range(&((RandomObject *)self)->rng, a, range); +} + +static double +Random_random_internal(PyObject *self) { + return random_double_impl(&((RandomObject *)self)->rng); +} + +static PyObject* +Random_randint(RandomObject *self, PyObject *const *args, Py_ssize_t nargs) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, + "randint() takes exactly 2 arguments (%zd given)", nargs); + return NULL; + } + + int64_t a = CPyLong_AsInt64(args[0]); + if (unlikely(a == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + + int64_t b = CPyLong_AsInt64(args[1]); + if (unlikely(b == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + + if (a > b) { + PyErr_SetString(PyExc_ValueError, + "empty range for randint()"); + return NULL; + } + + return randint_impl(&self->rng, a, b); +} + +static PyObject* +Random_randrange(RandomObject *self, PyObject *const *args, Py_ssize_t nargs) { + int64_t a, b; + if (parse_randrange_args(args, nargs, &a, &b) < 0) + return NULL; + return randint_impl(&self->rng, a, b); +} + +static PyObject* +Random_random(RandomObject *self, PyObject *Py_UNUSED(ignored)) { + return PyFloat_FromDouble(random_double_impl(&self->rng)); +} + +static PyObject* +Random_seed(RandomObject *self, PyObject *const *args, Py_ssize_t nargs) { + if (nargs != 1) { + PyErr_Format(PyExc_TypeError, + "seed() takes exactly 1 argument (%zd given)", nargs); + return NULL; + } + int64_t seed_val = CPyLong_AsInt64(args[0]); + if (unlikely(seed_val == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + chacha8_seed_int(&self->rng, seed_val); + Py_RETURN_NONE; +} + +static PyMethodDef Random_methods[] = { + {"randint", (PyCFunction) Random_randint, METH_FASTCALL, + PyDoc_STR("Return random integer in range [a, b], including both end points.") + }, + {"randrange", (PyCFunction) Random_randrange, METH_FASTCALL, + PyDoc_STR("Return random integer in range [start, stop).") + }, + {"random", (PyCFunction) Random_random, METH_NOARGS, + PyDoc_STR("Return random float in [0.0, 1.0).") + }, + {"seed", (PyCFunction) Random_seed, METH_FASTCALL, + PyDoc_STR("Seed the random number generator with an integer.") + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject RandomType = { + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "Random", + .tp_doc = PyDoc_STR("Fast random number generator using ChaCha8"), + .tp_basicsize = sizeof(RandomObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = Random_new, + .tp_init = (initproc) Random_init, + .tp_methods = Random_methods, +}; + +// Module definition + +static PyMethodDef librt_random_module_methods[] = { + {"random", (PyCFunction) module_random, METH_NOARGS, + PyDoc_STR("Return random float in [0.0, 1.0) using thread-local RNG.") + }, + {"randint", (PyCFunction) module_randint, METH_FASTCALL, + PyDoc_STR("Return random integer in range [a, b] using thread-local RNG.") + }, + {"randrange", (PyCFunction) module_randrange, METH_FASTCALL, + PyDoc_STR("Return random integer in range [start, stop) using thread-local RNG.") + }, + {"seed", (PyCFunction) module_seed, METH_FASTCALL, + PyDoc_STR("Seed the thread-local RNG with an integer.") + }, + {NULL, NULL, 0, NULL} +}; + +// Module-level internal functions for mypyc primitives (use thread-local RNG) + +static double +module_random_internal(void) { + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return CPY_FLOAT_ERROR; + return random_double_impl(rng); +} + +static int64_t +module_randint_internal(int64_t a, int64_t b) { + if (unlikely(a > b)) { + PyErr_SetString(PyExc_ValueError, "empty range for randint()"); + return CPY_LL_INT_ERROR; + } + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return CPY_LL_INT_ERROR; + uint64_t range = (uint64_t)b - (uint64_t)a + 1; + return random_i64_from_range(rng, a, range); +} + +static int64_t +module_randrange1_internal(int64_t stop) { + if (unlikely(stop <= 0)) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return CPY_LL_INT_ERROR; + } + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return CPY_LL_INT_ERROR; + return (int64_t)chacha8_next_ranged(rng, (uint64_t)stop); +} + +static int64_t +module_randrange2_internal(int64_t start, int64_t stop) { + if (unlikely(start >= stop)) { + PyErr_SetString(PyExc_ValueError, "empty range for randrange()"); + return CPY_LL_INT_ERROR; + } + chacha8_rng *rng = get_thread_rng(); + if (rng == NULL) + return CPY_LL_INT_ERROR; + uint64_t range = (uint64_t)stop - (uint64_t)start; + return random_i64_from_range(rng, start, range); +} + +static int +random_abi_version(void) { + return LIBRT_RANDOM_ABI_VERSION; +} + +static int +random_api_version(void) { + return LIBRT_RANDOM_API_VERSION; +} + +static int +librt_random_module_exec(PyObject *m) +{ + if (ensure_tls_key() < 0) { + return -1; + } + if (PyType_Ready(&RandomType) < 0) { + return -1; + } + if (PyModule_AddObjectRef(m, "Random", (PyObject *) &RandomType) < 0) { + return -1; + } + // Export mypyc internal C API via capsule + static void *librt_random_api[LIBRT_RANDOM_API_LEN] = { + (void *)random_abi_version, + (void *)random_api_version, + (void *)Random_internal, + (void *)Random_from_seed_internal, + (void *)Random_type_internal, + (void *)Random_random_internal, + (void *)Random_randint_internal, + (void *)Random_randrange1_internal, + (void *)Random_randrange2_internal, + (void *)module_random_internal, + (void *)module_randint_internal, + (void *)module_randrange1_internal, + (void *)module_randrange2_internal, + }; + PyObject *c_api_object = PyCapsule_New((void *)librt_random_api, "librt.random._C_API", NULL); + if (PyModule_Add(m, "_C_API", c_api_object) < 0) { + return -1; + } + return 0; +} + +static PyModuleDef_Slot librt_random_module_slots[] = { + {Py_mod_exec, librt_random_module_exec}, +#ifdef Py_MOD_GIL_NOT_USED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL} +}; + +static PyModuleDef librt_random_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "random", + .m_doc = "Fast random number generation using ChaCha8", + .m_size = 0, + .m_methods = librt_random_module_methods, + .m_slots = librt_random_module_slots, +}; + +PyMODINIT_FUNC +PyInit_random(void) +{ + return PyModuleDef_Init(&librt_random_module); +} diff --git a/mypyc/lib-rt/random/librt_random.h b/mypyc/lib-rt/random/librt_random.h new file mode 100644 index 0000000000000..2eabfbd021bc9 --- /dev/null +++ b/mypyc/lib-rt/random/librt_random.h @@ -0,0 +1,10 @@ +#ifndef LIBRT_RANDOM_H +#define LIBRT_RANDOM_H + +#include + +#define LIBRT_RANDOM_ABI_VERSION 1 +#define LIBRT_RANDOM_API_VERSION 9 +#define LIBRT_RANDOM_API_LEN 13 + +#endif // LIBRT_RANDOM_H diff --git a/mypyc/lib-rt/random/librt_random_api.c b/mypyc/lib-rt/random/librt_random_api.c new file mode 100644 index 0000000000000..157fa82b82eb3 --- /dev/null +++ b/mypyc/lib-rt/random/librt_random_api.c @@ -0,0 +1,45 @@ +#include + +#include "librt_random_api.h" + +void *LibRTRandom_API[LIBRT_RANDOM_API_LEN] = {0}; + +int +import_librt_random(void) +{ + PyObject *mod = PyImport_ImportModule("librt.random"); + if (mod == NULL) + return -1; + Py_DECREF(mod); // we import just for the side effect of making the below work. + void **capsule = (void **)PyCapsule_Import("librt.random._C_API", 0); + if (capsule == NULL) + return -1; + + // Only after version validation succeeds can we safely copy the full table. + int (*abi_version)(void) = (int (*)(void))capsule[0]; + int (*api_version)(void) = (int (*)(void))capsule[1]; + if (abi_version() != LIBRT_RANDOM_ABI_VERSION) { + char err[128]; + snprintf(err, sizeof(err), "ABI version conflict for librt.random, expected %d, found %d", + LIBRT_RANDOM_ABI_VERSION, + abi_version() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + if (api_version() < LIBRT_RANDOM_API_VERSION) { + char err[128]; + snprintf(err, sizeof(err), + "API version conflict for librt.random, expected %d or newer, found %d (hint: upgrade librt)", + LIBRT_RANDOM_API_VERSION, + api_version() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + // Provider API version is >= our expected version, which (by the API + // compatibility contract) means it has at least LIBRT_RANDOM_API_LEN + // entries, so this copy is safe. + memcpy(LibRTRandom_API, capsule, sizeof(LibRTRandom_API)); + return 0; +} diff --git a/mypyc/lib-rt/random/librt_random_api.h b/mypyc/lib-rt/random/librt_random_api.h new file mode 100644 index 0000000000000..2794de0dd7e58 --- /dev/null +++ b/mypyc/lib-rt/random/librt_random_api.h @@ -0,0 +1,32 @@ +#ifndef LIBRT_RANDOM_API_H +#define LIBRT_RANDOM_API_H + +#include +#include +#include +#include "librt_random.h" + +int +import_librt_random(void); + +extern void *LibRTRandom_API[LIBRT_RANDOM_API_LEN]; + +#define LibRTRandom_ABIVersion (*(int (*)(void)) LibRTRandom_API[0]) +#define LibRTRandom_APIVersion (*(int (*)(void)) LibRTRandom_API[1]) +#define LibRTRandom_Random_internal (*(PyObject* (*)(void)) LibRTRandom_API[2]) +#define LibRTRandom_Random_from_seed_internal (*(PyObject* (*)(int64_t)) LibRTRandom_API[3]) +#define LibRTRandom_Random_type_internal (*(PyTypeObject* (*)(void)) LibRTRandom_API[4]) +#define LibRTRandom_Random_random_internal (*(double (*)(PyObject*)) LibRTRandom_API[5]) +#define LibRTRandom_Random_randint_internal (*(int64_t (*)(PyObject*, int64_t, int64_t)) LibRTRandom_API[6]) +#define LibRTRandom_Random_randrange1_internal (*(int64_t (*)(PyObject*, int64_t)) LibRTRandom_API[7]) +#define LibRTRandom_Random_randrange2_internal (*(int64_t (*)(PyObject*, int64_t, int64_t)) LibRTRandom_API[8]) +#define LibRTRandom_module_random_internal (*(double (*)(void)) LibRTRandom_API[9]) +#define LibRTRandom_module_randint_internal (*(int64_t (*)(int64_t, int64_t)) LibRTRandom_API[10]) +#define LibRTRandom_module_randrange1_internal (*(int64_t (*)(int64_t)) LibRTRandom_API[11]) +#define LibRTRandom_module_randrange2_internal (*(int64_t (*)(int64_t, int64_t)) LibRTRandom_API[12]) + +static inline bool CPyRandom_Check(PyObject *obj) { + return Py_TYPE(obj) == LibRTRandom_Random_type_internal(); +} + +#endif // LIBRT_RANDOM_API_H diff --git a/mypyc/lib-rt/setup.py b/mypyc/lib-rt/setup.py index 49b6c10201317..15d31a443ff86 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -151,5 +151,24 @@ def run(self) -> None: Extension( "librt.time", ["time/librt_time.c"], include_dirs=["."], extra_compile_args=cflags ), + Extension( + "librt.random", + [ + "random/librt_random.c", + "init.c", + "int_ops.c", + "exc_ops.c", + "pythonsupport.c", + "getargsfast.c", + ], + include_dirs=["."], + extra_compile_args=cflags, + ), + Extension( + "librt.threading", + ["threading/librt_threading.c"], + include_dirs=[".", "threading"], + extra_compile_args=cflags, + ), ] ) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 3f08b5ef43766..b79ffcd9f7c89 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1153,6 +1153,56 @@ read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index)); } +// Python-level wrappers (`cp_*`) for interpreted callers. The C-side names +// are prefixed `cp_` to avoid colliding with libc's isspace etc. +// The LibRTStrings_Is* helpers themselves are static inline in librt_strings.h +// so they compile directly into mypyc-emitted code with no capsule +// indirection. + +// Parse a Python int as i32 codepoint. Returns 0 on success and writes +// the value to *out; returns -1 on error with a Python exception set. +static int +cp_parse_i32(PyObject *arg, int32_t *out) { + int overflow; + long c = PyLong_AsLongAndOverflow(arg, &overflow); + if (c == -1 && PyErr_Occurred()) + return -1; + if (overflow != 0 || c < INT32_MIN || c > INT32_MAX) { + PyErr_SetString(PyExc_OverflowError, + "codepoint out of i32 range"); + return -1; + } + *out = (int32_t)c; + return 0; +} + +#define DEFINE_CP_BOOL_WRAPPER(name, fn) \ + static PyObject* \ + cp_##name(PyObject *module, PyObject *arg) { \ + int32_t c; \ + if (cp_parse_i32(arg, &c) < 0) \ + return NULL; \ + return PyBool_FromLong(fn(c)); \ + } + +DEFINE_CP_BOOL_WRAPPER(isspace, LibRTStrings_IsSpace) +DEFINE_CP_BOOL_WRAPPER(isdigit, LibRTStrings_IsDigit) +DEFINE_CP_BOOL_WRAPPER(isalnum, LibRTStrings_IsAlnum) +DEFINE_CP_BOOL_WRAPPER(isalpha, LibRTStrings_IsAlpha) +DEFINE_CP_BOOL_WRAPPER(isidentifier, LibRTStrings_IsIdentifier) + +#define DEFINE_CP_I32_WRAPPER(name, fn) \ + static PyObject* \ + cp_##name(PyObject *module, PyObject *arg) { \ + int32_t c; \ + if (cp_parse_i32(arg, &c) < 0) \ + return NULL; \ + return PyLong_FromLong((long) fn(c)); \ + } + +DEFINE_CP_I32_WRAPPER(toupper, LibRTStrings_ToUpper) +DEFINE_CP_I32_WRAPPER(tolower, LibRTStrings_ToLower) + static PyMethodDef librt_strings_module_methods[] = { {"write_i16_le", (PyCFunction) write_i16_le, METH_FASTCALL, PyDoc_STR("Write a 16-bit signed integer to BytesWriter in little-endian format") @@ -1214,6 +1264,27 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL, PyDoc_STR("Read a 64-bit float from bytes in big-endian format") }, + {"isspace", cp_isspace, METH_O, + PyDoc_STR("Test whether a codepoint (i32) is Unicode whitespace.") + }, + {"isdigit", cp_isdigit, METH_O, + PyDoc_STR("Test whether a codepoint (i32) is a Unicode digit.") + }, + {"isalnum", cp_isalnum, METH_O, + PyDoc_STR("Test whether a codepoint (i32) is alphanumeric.") + }, + {"isalpha", cp_isalpha, METH_O, + PyDoc_STR("Test whether a codepoint (i32) is a Unicode letter.") + }, + {"isidentifier", cp_isidentifier, METH_O, + PyDoc_STR("Test whether a codepoint (i32) is a valid identifier start (XID_Start).") + }, + {"toupper", cp_toupper, METH_O, + PyDoc_STR("Single-codepoint uppercase mapping for a codepoint (i32). Returns the input unchanged if the Unicode uppercase expands to multiple codepoints (e.g. U+00DF uppercases to \"SS\"); use str.upper() for full Unicode case conversion.") + }, + {"tolower", cp_tolower, METH_O, + PyDoc_STR("Single-codepoint lowercase mapping for a codepoint (i32). Returns the input unchanged if the Unicode lowercase expands to multiple codepoints; use str.lower() for full Unicode case conversion.") + }, {NULL, NULL, 0, NULL} }; diff --git a/mypyc/lib-rt/strings/librt_strings.h b/mypyc/lib-rt/strings/librt_strings.h index 0bf1587e6f44f..33af57dc2a2a8 100644 --- a/mypyc/lib-rt/strings/librt_strings.h +++ b/mypyc/lib-rt/strings/librt_strings.h @@ -1,8 +1,10 @@ #ifndef LIBRT_STRINGS_H #define LIBRT_STRINGS_H -#include #include +#include +#include +#include "CPy.h" #include "librt_strings_common.h" // ABI version -- only an exact match is compatible. This will only be changed in @@ -28,4 +30,102 @@ typedef struct { char data[WRITER_EMBEDDED_BUF_LEN]; // Default buffer } StringWriterObject; +// Codepoint classification helpers. Inputs are signed i32 for compatibility +// with mypyc's int32_rprimitive; out-of-range values (negative, or past the +// maximum Unicode code point 0x10FFFF) are non-codepoints and return false. +// Defined `static inline` so they compile statically into +// both the librt.strings module and any mypyc-compiled extension that +// includes this header, avoiding the capsule indirection that would dwarf +// the work of a single Py_UNICODE_IS* macro call. + +static inline bool LibRTStrings_IsSpace(int32_t c) { + return c >= 0 && Py_UNICODE_ISSPACE((Py_UCS4)c); +} + +static inline bool LibRTStrings_IsDigit(int32_t c) { + return c >= 0 && Py_UNICODE_ISDIGIT((Py_UCS4)c); +} + +static inline bool LibRTStrings_IsAlnum(int32_t c) { + return c >= 0 && Py_UNICODE_ISALNUM((Py_UCS4)c); +} + +static inline bool LibRTStrings_IsAlpha(int32_t c) { + return c >= 0 && Py_UNICODE_ISALPHA((Py_UCS4)c); +} + +// True if c could start a valid identifier (XID_Start, per PEP 3131). +// ASCII fast path covers `[A-Za-z_]`; non-ASCII delegates to CPython's +// PyUnicode_IsIdentifier on a 1-character string. Aborts via +// CPyError_OutOfMemory on allocation failure to keep this ERR_NEVER. +static inline bool LibRTStrings_IsIdentifier(int32_t c) { + // Unsigned compare: negatives wrap to large values and skip the fast path. + if ((uint32_t)c < 128) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_'; + } + // Reject negatives and code points past the Unicode maximum. + if ((uint32_t)c > 0x10FFFF) return false; + PyObject *s = PyUnicode_FromOrdinal((int)c); + if (s == NULL) { + CPyError_OutOfMemory(); + } + int r = PyUnicode_IsIdentifier(s); + Py_DECREF(s); + return r == 1; +} + +// Shared slow path for LibRTStrings_ToUpper / _ToLower. Round-trips the +// codepoint through CPython's str.upper / str.lower on a 1-character +// string. When the conversion expands to multiple codepoints (e.g. +// 'ß'.upper() == 'SS') we return the input unchanged so the public +// helpers stay i32 -> i32. Aborts via CPyError_OutOfMemory on allocation +// failure. +static inline int32_t LibRTStrings_ChangeCase_slow(int32_t c, const char *method) { + PyObject *s = PyUnicode_FromOrdinal((int)c); + if (s == NULL) { + CPyError_OutOfMemory(); + } + PyObject *u = PyObject_CallMethod(s, method, NULL); + Py_DECREF(s); + if (u == NULL) { + CPyError_OutOfMemory(); + } + int32_t result = c; + if (PyUnicode_GET_LENGTH(u) == 1) { + result = (int32_t)PyUnicode_READ_CHAR(u, 0); + } + Py_DECREF(u); + return result; +} + +// Uppercase a codepoint. ASCII fast path is `a..z -> A..Z` (subtract 32); +// non-ASCII delegates to str.upper on a 1-character string. Returns the +// input unchanged when uppercasing expands to multiple codepoints. +static inline int32_t LibRTStrings_ToUpper(int32_t c) { + // Unsigned compare: negatives wrap to large values and skip the fast path. + if ((uint32_t)c < 128) { + if (c >= 'a' && c <= 'z') return c - 32; + return c; + } + // Negatives and code points past the Unicode maximum are returned unchanged. + if ((uint32_t)c > 0x10FFFF) return c; + return LibRTStrings_ChangeCase_slow(c, "upper"); +} + +// Lowercase a codepoint. ASCII fast path is `A..Z -> a..z` (add 32); +// non-ASCII delegates to str.lower on a 1-character string. Returns the +// input unchanged when lowercasing expands to multiple codepoints. +static inline int32_t LibRTStrings_ToLower(int32_t c) { + // Unsigned compare: negatives wrap to large values and skip the fast path. + if ((uint32_t)c < 128) { + if (c >= 'A' && c <= 'Z') return c + 32; + return c; + } + // Negatives and code points past the Unicode maximum are returned unchanged. + if ((uint32_t)c > 0x10FFFF) return c; + return LibRTStrings_ChangeCase_slow(c, "lower"); +} + #endif // LIBRT_STRINGS_H diff --git a/mypyc/lib-rt/strings/librt_strings_api.h b/mypyc/lib-rt/strings/librt_strings_api.h index f0bb761bcaa2e..536b90ad7f21c 100644 --- a/mypyc/lib-rt/strings/librt_strings_api.h +++ b/mypyc/lib-rt/strings/librt_strings_api.h @@ -4,8 +4,8 @@ int import_librt_strings(void); -#include #include +#include #include "librt_strings.h" extern void *LibRTStrings_API[LIBRT_STRINGS_API_LEN]; diff --git a/mypyc/lib-rt/stringwriter_extra_ops.h b/mypyc/lib-rt/stringwriter_extra_ops.h index 0da9a4d9d7f71..bac6dd6b3e95c 100644 --- a/mypyc/lib-rt/stringwriter_extra_ops.h +++ b/mypyc/lib-rt/stringwriter_extra_ops.h @@ -1,9 +1,9 @@ #ifndef STRINGWRITER_EXTRA_OPS_H #define STRINGWRITER_EXTRA_OPS_H +#include #include #include -#include #include "mypyc_util.h" #include "strings/librt_strings_api.h" diff --git a/mypyc/lib-rt/test_capi.cc b/mypyc/lib-rt/test_capi.cc index 4b183de5743e9..bf15a47e63dd1 100644 --- a/mypyc/lib-rt/test_capi.cc +++ b/mypyc/lib-rt/test_capi.cc @@ -1,8 +1,8 @@ // Test cases -#include #include #include "CPy.h" +#include static PyObject *moduleDict; diff --git a/mypyc/lib-rt/threading/librt_threading.c b/mypyc/lib-rt/threading/librt_threading.c new file mode 100644 index 0000000000000..84d7346a75872 --- /dev/null +++ b/mypyc/lib-rt/threading/librt_threading.c @@ -0,0 +1,714 @@ +#include "pythoncapi_compat.h" + +#define PY_SSIZE_T_CLEAN +#include +#include "librt_threading.h" +#include "mypyc_util.h" + +#if !defined(_WIN32) +#include +#include +#endif + +#if CPY_3_14_FEATURES + +// Python 3.14+ (all platforms, with or without the GIL): Use PyMutex (1-byte +// atomic lock with parking lot). PyMutex gives better interruptibility than the +// pthread fallback, and its fast release path is competitive with sem_t. +// PyMutex_LockFast, _PyMutex_LockTimed, and _PY_LOCK_DETACH are internal +// CPython APIs that might change across minor releases. +#define LOCK_BACKEND_PYMUTEX +#ifndef Py_BUILD_CORE +#define Py_BUILD_CORE +#endif +#include "internal/pycore_lock.h" + +#elif defined(_WIN32) + +// Python <3.14 on Windows: Use Slim Reader/Writer Lock +#define LOCK_BACKEND_SRWLOCK +#include + +#else + +// Python <3.14 on POSIX. +// +// Prefer a POSIX unnamed semaphore when the platform supports it well and the +// GIL is enabled, and fall back to a pthread mutex + condition variable +// otherwise. We use the same test CPython uses to pick its semaphore-based lock +// (see Python/thread_pthread.h): an unnamed semaphore is only usable when +// sem_init() actually works AND a timed wait is available. Notably this is true +// on Linux but false on macOS (whose sem_init() is a non-functional stub), so +// macOS uses the mutex+condvar fallback. Free-threaded builds also use the +// mutex+condvar fallback because the semaphore backend's `locked` bookkeeping +// relies on GIL serialization. +#if !defined(Py_GIL_DISABLED) && \ + defined(_POSIX_SEMAPHORES) && (_POSIX_SEMAPHORES + 0) != -1 && \ + (defined(HAVE_SEM_TIMEDWAIT) || defined(HAVE_SEM_CLOCKWAIT)) +#define LOCK_BACKEND_SEM +#include +#else +#define LOCK_BACKEND_PTHREAD +#include +#endif + +#endif + +// +// Lock +// +// A fast mutex lock for use from mypyc-compiled code. +// +// On Python 3.14+ (all platforms), this uses CPython's PyMutex, a 1-byte atomic +// lock backed by a parking lot for contended waits. PyMutex automatically +// releases the GIL when blocking. +// +// On Python 3.13 and earlier with Windows, this uses an SRWLOCK (Slim +// Reader/Writer Lock) plus a CONDITION_VARIABLE guarding a `locked` flag. The SRWLOCK only +// protects the flag and is never held across the user's critical section, so +// release() may be called from a thread other than the acquirer (matching +// threading.Lock semantics). This mirrors CPython's Windows lock (NRMUTEX in +// Python/thread_nt.h) and is the Windows twin of the POSIX pthread+condvar +// backend below. +// +// On Python 3.13 and earlier with POSIX systems, there are two backends, both +// of which allow release() from a thread other than the one that acquired the +// lock (matching threading.Lock semantics): +// +// - Where unnamed POSIX semaphores work well (e.g. Linux) and the GIL is +// enabled, this uses a sem_t initialized to 1: acquire is sem_wait, release +// is sem_post. Semaphores have no ownership concept, so cross-thread release +// is directly well-defined. +// +// - Otherwise (e.g. macOS, whose sem_init() is a non-functional stub, and +// free-threaded builds), this uses a pthread mutex + condition variable +// guarding a `locked` flag. The mutex only protects the flag and is never +// held across the user's critical section, so the OS mutex is always +// unlocked on the same thread that locked it. +// + +// ---------- Platform-specific lock state ---------- + +#if defined(LOCK_BACKEND_PYMUTEX) + +typedef struct { + PyObject_HEAD + PyMutex mutex; +} LockObject; + +#elif defined(LOCK_BACKEND_SRWLOCK) + +typedef struct { + PyObject_HEAD + // The SRWLOCK below does NOT represent the Python lock; like the pthread + // fallback's `mut`, it only guards `locked` and the condition variable, + // and is held just long enough to inspect/flip the flag -- never across + // the user's critical section. That is what allows release() from a + // thread other than the acquirer: SRWLOCK's same-thread-release rule is + // never violated, and clearing `locked` is just a guarded store. This + // mirrors CPython's Windows lock (NRMUTEX in Python/thread_nt.h). + SRWLOCK srw; + CONDITION_VARIABLE lock_released; + int locked; // 0=unlocked, 1=locked; protected by `srw` +} LockObject; + +#elif defined(LOCK_BACKEND_SEM) + +typedef struct { + PyObject_HEAD + sem_t sem; // counting semaphore, initialized to 1 + // Tracks the locked state for locked() and release-unlocked detection. + // The semaphore itself is the source of truth for mutual exclusion; this + // flag is advisory bookkeeping. It is set after a successful acquire and + // cleared before sem_post in release. + // + // This backend is only selected when the GIL is enabled, so this flag is a + // plain int relying on the GIL for serialization: it is only ever touched + // with the GIL held (the blocking sem_wait drops the GIL, but the flag + // store happens after the GIL is reacquired). This mirrors the old + // PyThread_type_lock wrapper bookkeeping used by CPython 3.12 and earlier, + // where _thread lock kept a plain `char locked` for sanity checks and + // locked(). + int locked; +} LockObject; + +#else // pthread mutex + condvar fallback + +typedef struct { + PyObject_HEAD + // The pthread mutex below does NOT represent the Python lock; it only + // guards the `locked` flag and the condition variable. The Python lock + // state is `locked` itself. This indirection (matching CPython's POSIX + // lock) is what allows release() from a thread other than the acquirer: + // `mut` is always locked and unlocked within a single call on a single + // thread, so pthread's same-thread-unlock rule is never violated. + pthread_mutex_t mut; + pthread_cond_t lock_released; + // Always accessed while holding `mut` (including the locked() reader), + // so a plain int is sufficient -- the mutex provides the ordering. + // Matches CPython's pthread_lock.locked (a plain char). + int locked; // 0=unlocked, 1=locked; protected by `mut` +} LockObject; + +#endif + +// ---------- Platform-specific init/acquire/release ---------- + +static inline int +Lock_init_internal(LockObject *self) +{ +#if defined(LOCK_BACKEND_PYMUTEX) + self->mutex = (PyMutex){0}; +#elif defined(LOCK_BACKEND_SRWLOCK) + InitializeSRWLock(&self->srw); + InitializeConditionVariable(&self->lock_released); + self->locked = 0; +#elif defined(LOCK_BACKEND_SEM) + if (sem_init(&self->sem, 0, 1) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + self->locked = 0; +#else + int status = pthread_mutex_init(&self->mut, NULL); + if (status != 0) { + errno = status; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + status = pthread_cond_init(&self->lock_released, NULL); + if (status != 0) { + pthread_mutex_destroy(&self->mut); + errno = status; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + self->locked = 0; +#endif + return 0; +} + +// Try to acquire the lock. Returns 1 (true) on success, 0 (false) if +// non-blocking and the lock is held, or -1 if interrupted by an error-raising +// signal handler. +static int +Lock_acquire_impl(LockObject *self, int blocking) +{ +#if defined(LOCK_BACKEND_PYMUTEX) + if (!blocking) { + PyLockStatus r = _PyMutex_LockTimed(&self->mutex, 0, _Py_LOCK_DONT_DETACH); + return r == PY_LOCK_ACQUIRED; + } + if (PyMutex_LockFast(&self->mutex)) { + return 1; + } + PyLockStatus r = _PyMutex_LockTimed(&self->mutex, -1, + _PY_LOCK_DETACH | _PY_LOCK_HANDLE_SIGNALS); + if (r == PY_LOCK_INTR) { + return -1; + } + return 1; + +#elif defined(LOCK_BACKEND_SRWLOCK) + // `srw` only guards `locked` and the condition variable; it is held just + // long enough to inspect/flip the flag, never across the user's critical + // section. This is what lets a different thread call release(). + // + // Fast path: grab the lock without releasing the GIL if it is free. This is + // also the whole story in the non-blocking case. + AcquireSRWLockExclusive(&self->srw); + if (!self->locked) { + self->locked = 1; + ReleaseSRWLockExclusive(&self->srw); + return 1; + } + ReleaseSRWLockExclusive(&self->srw); + if (!blocking) { + return 0; + } + + // Slow path: wait for the lock to be released, with the GIL dropped so + // other Python threads can run (and release the lock). + // SleepConditionVariableSRW atomically releases the SRWLOCK while sleeping + // and reacquires it on wake, exactly like pthread_cond_wait. + Py_BEGIN_ALLOW_THREADS + AcquireSRWLockExclusive(&self->srw); + while (self->locked) { + SleepConditionVariableSRW(&self->lock_released, &self->srw, INFINITE, 0); + } + self->locked = 1; + ReleaseSRWLockExclusive(&self->srw); + Py_END_ALLOW_THREADS + return 1; + +#elif defined(LOCK_BACKEND_SEM) + // A semaphore has no ownership: any thread may sem_post a token that + // another thread consumed via sem_wait, so cross-thread release is + // directly well-defined. `locked` is advisory bookkeeping for locked() + // and for guarding against releasing an unheld lock. + // + // Fast path: try a non-blocking acquire first to avoid GIL release/reacquire + // overhead in the common uncontended case. This is also the whole story in + // the non-blocking case. + { + int status; + do { + status = sem_trywait(&self->sem); + } while (status == -1 && errno == EINTR); + if (status == 0) { + self->locked = 1; + return 1; + } + } + if (!blocking) { + return 0; // EAGAIN: already held + } + + // Slow path: block with the GIL dropped so other Python threads can run + // (and release the lock). If a signal interrupts sem_wait(), run pending + // Python signal handlers with the GIL held; retry unless a handler raises. + for (;;) { + int status; + int err = 0; + + Py_BEGIN_ALLOW_THREADS + status = sem_wait(&self->sem); + if (status == -1) { + err = errno; + } + Py_END_ALLOW_THREADS + + if (status == 0) { + self->locked = 1; + return 1; + } + + if (err != EINTR) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + if (Py_MakePendingCalls() < 0) { + return -1; + } + } + +#else // pthread mutex + condvar fallback + // `mut` only guards `locked` and the condition variable; it is held just + // long enough to inspect/flip the flag, never across the user's critical + // section. This is what lets a different thread call release(). + // + // Fast path: grab the lock without releasing the GIL if it is free. This is + // also the whole story in the non-blocking case. + pthread_mutex_lock(&self->mut); + if (!self->locked) { + self->locked = 1; + pthread_mutex_unlock(&self->mut); + return 1; + } + pthread_mutex_unlock(&self->mut); + if (!blocking) { + return 0; + } + + // Slow path: wait for the lock to be released, with the GIL dropped so + // other Python threads can run (and release the lock). If we wake but do + // not get the lock, give pending Python signal handlers a chance to run, + // matching CPython's pthread fallback. + for (;;) { + int acquired = 0; + int interrupted = 0; + int status; + + Py_BEGIN_ALLOW_THREADS + status = pthread_mutex_lock(&self->mut); + if (status == 0) { + while (self->locked) { + status = pthread_cond_wait(&self->lock_released, &self->mut); + if (status != 0) { + break; + } + if (self->locked) { + interrupted = 1; + break; + } + } + if (status == 0 && !interrupted) { + self->locked = 1; + acquired = 1; + } + int unlock_status = pthread_mutex_unlock(&self->mut); + if (status == 0) { + status = unlock_status; + } + } + Py_END_ALLOW_THREADS + + if (status != 0) { + errno = status; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + if (acquired) { + return 1; + } + if (interrupted && Py_MakePendingCalls() < 0) { + return -1; + } + } +#endif +} + +// Release the lock. Returns 0 on success, -1 if the lock was not held. +static int +Lock_release_impl(LockObject *self) +{ +#if defined(LOCK_BACKEND_PYMUTEX) + // threading.Lock is unowned, so release() may be called from a different + // thread than acquire(). CPython's atomic _PyMutex_TryUnlock() is not part + // of the public API and is not exported by all CPython builds. This fast + // partial-replacement path deliberately avoids a second guard mutex: + // ordinary release() of an unlocked lock raises RuntimeError, but racy + // erroneous release() calls are undefined behavior and may make + // PyMutex_Unlock() abort. + if (!PyMutex_IsLocked(&self->mutex)) { + return -1; + } + PyMutex_Unlock(&self->mutex); + return 0; + +#elif defined(LOCK_BACKEND_SRWLOCK) + AcquireSRWLockExclusive(&self->srw); + if (!self->locked) { + ReleaseSRWLockExclusive(&self->srw); + return -1; + } + self->locked = 0; + // Wake one waiter (if any). Signalling under `srw` is fine and avoids a + // lost-wakeup race. + WakeConditionVariable(&self->lock_released); + ReleaseSRWLockExclusive(&self->srw); + return 0; + +#elif defined(LOCK_BACKEND_SEM) + // Check-then-clear the flag, then post. This backend is only selected when + // the GIL is enabled, so the check and clear are serialized without an + // atomic. + if (!self->locked) { + return -1; + } + self->locked = 0; + sem_post(&self->sem); + return 0; + +#else // pthread mutex + condvar fallback + pthread_mutex_lock(&self->mut); + if (!self->locked) { + pthread_mutex_unlock(&self->mut); + return -1; + } + self->locked = 0; + // Wake one waiter (if any). Signalling under `mut` is fine and avoids a + // lost-wakeup race. + pthread_cond_signal(&self->lock_released); + pthread_mutex_unlock(&self->mut); + return 0; +#endif +} + +static inline int +Lock_is_locked(LockObject *self) +{ +#if defined(LOCK_BACKEND_PYMUTEX) + return PyMutex_IsLocked(&self->mutex); +#elif defined(LOCK_BACKEND_SRWLOCK) + // locked() is not expected to be on a perf-critical path, so take the + // SRWLOCK (shared) for a clean read of the guarded flag rather than + // relying on an atomic/volatile field. + AcquireSRWLockShared(&self->srw); + int result = self->locked; + ReleaseSRWLockShared(&self->srw); + return result; +#elif defined(LOCK_BACKEND_SEM) + // The flag is GIL-serialized (see the struct comment); locked() is a + // plain read, matching the old CPython _thread lock bookkeeping described + // above. + return self->locked != 0; +#else // pthread mutex + condvar fallback + // `locked` is only ever accessed under `mut`; take it for a clean read. + // locked() is not expected to be on a perf-critical path. + pthread_mutex_lock(&self->mut); + int result = self->locked; + pthread_mutex_unlock(&self->mut); + return result; +#endif +} + +// ---------- Python type methods (shared across platforms) ---------- + +static PyTypeObject LockType; + +static PyObject * +Lock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + if (type != &LockType) { + PyErr_SetString(PyExc_TypeError, "Lock cannot be subclassed"); + return NULL; + } + + LockObject *self = (LockObject *)type->tp_alloc(type, 0); + if (self != NULL && Lock_init_internal(self) < 0) { + type->tp_free((PyObject *)self); + return NULL; + } + return (PyObject *)self; +} + +static int +Lock_init(LockObject *self, PyObject *args, PyObject *kwds) +{ + if (!PyArg_ParseTuple(args, "")) { + return -1; + } + + if (kwds != NULL && PyDict_Size(kwds) > 0) { + PyErr_SetString(PyExc_TypeError, + "Lock() takes no keyword arguments"); + return -1; + } + + return 0; +} + +static void +Lock_dealloc(LockObject *self) +{ +#if defined(LOCK_BACKEND_SEM) + sem_destroy(&self->sem); +#elif defined(LOCK_BACKEND_PTHREAD) + // `mut` is only ever held transiently within a single call, so it is + // always unlocked here even if the Python lock is still "locked". + // Some pthread implementations require the cond to be destroyed first. + pthread_cond_destroy(&self->lock_released); + pthread_mutex_destroy(&self->mut); +#endif + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +Lock_acquire(LockObject *self, PyObject *const *args, Py_ssize_t nargs, + PyObject *kwnames) +{ + int blocking = 1; + + Py_ssize_t nkw = kwnames ? PyTuple_GET_SIZE(kwnames) : 0; + if (nargs + nkw > 1) { + PyErr_SetString(PyExc_TypeError, "acquire() takes at most 1 argument"); + return NULL; + } + + if (nargs == 1) { + blocking = PyObject_IsTrue(args[0]); + if (blocking < 0) + return NULL; + } else if (nkw == 1) { + PyObject *key = PyTuple_GET_ITEM(kwnames, 0); + if (PyUnicode_CompareWithASCIIString(key, "blocking") != 0) { + PyErr_Format(PyExc_TypeError, + "acquire() got an unexpected keyword argument '%U'", + key); + return NULL; + } + blocking = PyObject_IsTrue(args[0]); + if (blocking < 0) + return NULL; + } + + int result = Lock_acquire_impl(self, blocking); + if (result < 0) { + return NULL; + } + return PyBool_FromLong(result); +} + +static PyObject * +Lock_release(LockObject *self, PyObject *Py_UNUSED(ignored)) +{ + if (Lock_release_impl(self) < 0) { + PyErr_SetString(PyExc_RuntimeError, "cannot release an unlocked lock"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +Lock_locked(LockObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyBool_FromLong(Lock_is_locked(self)); +} + +static PyObject * +Lock_enter(LockObject *self, PyObject *Py_UNUSED(ignored)) +{ + int result = Lock_acquire_impl(self, 1); + if (result < 0) + return NULL; + return PyBool_FromLong(result); +} + +static PyObject * +Lock_exit(LockObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + return Lock_release(self, NULL); +} + +static PyMethodDef Lock_methods[] = { + {"acquire", (PyCFunction)(void(*)(void))Lock_acquire, METH_FASTCALL | METH_KEYWORDS, + PyDoc_STR("Acquire the lock, blocking or non-blocking.\n" + "Returns True if the lock was acquired, False otherwise.")}, + {"release", (PyCFunction)Lock_release, METH_NOARGS, + PyDoc_STR("Release the lock.")}, + {"locked", (PyCFunction)Lock_locked, METH_NOARGS, + PyDoc_STR("Return True if the lock is currently held.")}, + {"__enter__", (PyCFunction)Lock_enter, METH_NOARGS, + PyDoc_STR("Acquire the lock.")}, + {"__exit__", (PyCFunction)Lock_exit, METH_FASTCALL, + PyDoc_STR("Release the lock.")}, + {NULL} +}; + +static PyTypeObject LockType = { + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "Lock", + .tp_doc = PyDoc_STR("A fast mutual exclusion lock"), + .tp_basicsize = sizeof(LockObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = Lock_new, + .tp_init = (initproc)Lock_init, + .tp_dealloc = (destructor)Lock_dealloc, + .tp_methods = Lock_methods, +}; + +static PyTypeObject * +Lock_type_internal(void) { + return &LockType; +} + +// Create a new Lock object (for use from compiled code) +static PyObject * +Lock_new_internal(void) { + LockObject *self = (LockObject *)LockType.tp_alloc(&LockType, 0); + if (self != NULL && Lock_init_internal(self) < 0) { + LockType.tp_free((PyObject *)self); + return NULL; + } + return (PyObject *)self; +} + +// Acquire the lock (blocking), for use from compiled code. +// Returns true on success, sets error and returns 2 (ERR_MAGIC) on failure. +static char +Lock_acquire_internal(PyObject *self) { + int result = Lock_acquire_impl((LockObject *)self, 1); + if (result < 0) { + return 2; + } + return (char)result; +} + +// Acquire the lock with explicit blocking arg, for use from compiled code. +// Returns true if acquired, false otherwise. Sets error and returns 2 +// (ERR_MAGIC) on failure. +static char +Lock_acquire_blocking_internal(PyObject *self, char blocking) { + int result = Lock_acquire_impl((LockObject *)self, blocking); + if (result < 0) { + return 2; + } + return (char)result; +} + +// Release the lock, for use from compiled code. +// Returns 0 (None) on success, sets error and returns 2 (ERR_MAGIC) on failure. +static char +Lock_release_internal(PyObject *self) { + if (Lock_release_impl((LockObject *)self) < 0) { + PyErr_SetString(PyExc_RuntimeError, "cannot release an unlocked lock"); + return 2; + } + return 0; +} + +// Check if the lock is held, for use from compiled code. +static char +Lock_locked_internal(PyObject *self) { + return (char)Lock_is_locked((LockObject *)self); +} + +static PyMethodDef librt_threading_module_methods[] = { + {NULL, NULL, 0, NULL} +}; + +static int +threading_abi_version(void) { + return LIBRT_THREADING_ABI_VERSION; +} + +static int +threading_api_version(void) { + return LIBRT_THREADING_API_VERSION; +} + +static int +librt_threading_module_exec(PyObject *m) +{ + if (PyType_Ready(&LockType) < 0) { + return -1; + } + if (PyModule_AddObjectRef(m, "Lock", (PyObject *)&LockType) < 0) { + return -1; + } + + // Export mypyc internal C API via capsule + static void *threading_api[LIBRT_THREADING_API_LEN] = { + (void *)threading_abi_version, + (void *)threading_api_version, + (void *)Lock_type_internal, + (void *)Lock_new_internal, + (void *)Lock_acquire_internal, + (void *)Lock_release_internal, + (void *)Lock_locked_internal, + (void *)Lock_acquire_blocking_internal, + }; + PyObject *c_api_object = PyCapsule_New((void *)threading_api, "librt.threading._C_API", NULL); + if (PyModule_Add(m, "_C_API", c_api_object) < 0) { + return -1; + } + return 0; +} + +static PyModuleDef_Slot librt_threading_module_slots[] = { + {Py_mod_exec, librt_threading_module_exec}, +#ifdef Py_MOD_GIL_NOT_USED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL} +}; + +static PyModuleDef librt_threading_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "threading", + .m_doc = "Fast threading primitives optimized for mypyc", + .m_size = 0, + .m_methods = librt_threading_module_methods, + .m_slots = librt_threading_module_slots, +}; + +PyMODINIT_FUNC +PyInit_threading(void) +{ + return PyModuleDef_Init(&librt_threading_module); +} diff --git a/mypyc/lib-rt/threading/librt_threading.h b/mypyc/lib-rt/threading/librt_threading.h new file mode 100644 index 0000000000000..a22284f3fdbcd --- /dev/null +++ b/mypyc/lib-rt/threading/librt_threading.h @@ -0,0 +1,10 @@ +#ifndef LIBRT_THREADING_H +#define LIBRT_THREADING_H + +#include + +#define LIBRT_THREADING_ABI_VERSION 1 +#define LIBRT_THREADING_API_VERSION 1 +#define LIBRT_THREADING_API_LEN 8 + +#endif // LIBRT_THREADING_H diff --git a/mypyc/lib-rt/threading/librt_threading_api.c b/mypyc/lib-rt/threading/librt_threading_api.c new file mode 100644 index 0000000000000..156975d243638 --- /dev/null +++ b/mypyc/lib-rt/threading/librt_threading_api.c @@ -0,0 +1,43 @@ +#include "librt_threading_api.h" + +void *LibRTThreading_API[LIBRT_THREADING_API_LEN] = {0}; + +int +import_librt_threading(void) +{ + PyObject *mod = PyImport_ImportModule("librt.threading"); + if (mod == NULL) + return -1; + Py_DECREF(mod); // we import just for the side effect of making the below work. + void **capsule = (void **)PyCapsule_Import("librt.threading._C_API", 0); + if (capsule == NULL) + return -1; + + // Only after version validation succeeds can we safely copy the full table. + int (*abi_version)(void) = (int (*)(void))capsule[0]; + int (*api_version)(void) = (int (*)(void))capsule[1]; + if (abi_version() != LIBRT_THREADING_ABI_VERSION) { + char err[128]; + snprintf(err, sizeof(err), "ABI version conflict for librt.threading, expected %d, found %d", + LIBRT_THREADING_ABI_VERSION, + abi_version() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + if (api_version() < LIBRT_THREADING_API_VERSION) { + char err[128]; + snprintf(err, sizeof(err), + "API version conflict for librt.threading, expected %d or newer, found %d (hint: upgrade librt)", + LIBRT_THREADING_API_VERSION, + api_version() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + // Provider API version is >= our expected version, which (by the API + // compatibility contract) means it has at least LIBRT_THREADING_API_LEN + // entries, so this copy is safe. + memcpy(LibRTThreading_API, capsule, sizeof(LibRTThreading_API)); + return 0; +} diff --git a/mypyc/lib-rt/threading/librt_threading_api.h b/mypyc/lib-rt/threading/librt_threading_api.h new file mode 100644 index 0000000000000..acb3414d79561 --- /dev/null +++ b/mypyc/lib-rt/threading/librt_threading_api.h @@ -0,0 +1,20 @@ +#ifndef LIBRT_THREADING_API_H +#define LIBRT_THREADING_API_H + +#include "librt_threading.h" + +int +import_librt_threading(void); + +extern void *LibRTThreading_API[LIBRT_THREADING_API_LEN]; + +#define LibRTThreading_ABIVersion (*(int (*)(void)) LibRTThreading_API[0]) +#define LibRTThreading_APIVersion (*(int (*)(void)) LibRTThreading_API[1]) +#define LibRTThreading_Lock_type_internal (*(PyTypeObject* (*)(void)) LibRTThreading_API[2]) +#define LibRTThreading_Lock_new_internal (*(PyObject* (*)(void)) LibRTThreading_API[3]) +#define LibRTThreading_Lock_acquire_internal (*(char (*)(PyObject *self)) LibRTThreading_API[4]) +#define LibRTThreading_Lock_release_internal (*(char (*)(PyObject *self)) LibRTThreading_API[5]) +#define LibRTThreading_Lock_locked_internal (*(char (*)(PyObject *self)) LibRTThreading_API[6]) +#define LibRTThreading_Lock_acquire_blocking_internal (*(char (*)(PyObject *self, char blocking)) LibRTThreading_API[7]) + +#endif // LIBRT_THREADING_API_H diff --git a/mypyc/lib-rt/vecs/vec_t.c b/mypyc/lib-rt/vecs/vec_t.c index 3cfd1756201ce..5c0865ed7772e 100644 --- a/mypyc/lib-rt/vecs/vec_t.c +++ b/mypyc/lib-rt/vecs/vec_t.c @@ -756,6 +756,36 @@ static inline VecT vec_from_sequence( VecT v = vec_alloc(alloc_size, item_type); if (VEC_IS_ERROR(v)) return vec_error(); +#ifdef Py_GIL_DISABLED + if (is_list) { + // Other threads could be mutating the list concurrently, so use strong references + Py_ssize_t actual = 0; + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(seq) && i < n; i++) { + PyObject *item = PyList_GetItemRef(seq, i); + if (unlikely(item == NULL)) { + // Race condition: list shrank between size read and get item + for (Py_ssize_t j = actual; j < alloc_size; j++) + v.items[j] = NULL; + VEC_DECREF(v); + return vec_error(); + } + if (!VecT_ItemCheck(v, item, item_type)) { + Py_DECREF(item); + for (Py_ssize_t j = actual; j < alloc_size; j++) + v.items[j] = NULL; + VEC_DECREF(v); + return vec_error(); + } + v.items[actual++] = item; + } + for (Py_ssize_t j = actual; j < alloc_size; j++) + v.items[j] = NULL; + vec_track_buffer(&v); + v.len = actual; + return v; + } + // Tuples are immutable, so fall through to the shared loop below +#endif for (Py_ssize_t i = 0; i < n; i++) { PyObject *item = is_list ? PyList_GET_ITEM(seq, i) : PyTuple_GET_ITEM(seq, i); if (!VecT_ItemCheck(v, item, item_type)) { diff --git a/mypyc/lib-rt/vecs/vec_template.c b/mypyc/lib-rt/vecs/vec_template.c index b3e80261fec38..a6b09ba2a7b29 100644 --- a/mypyc/lib-rt/vecs/vec_template.c +++ b/mypyc/lib-rt/vecs/vec_template.c @@ -148,6 +148,30 @@ static inline VEC vec_from_sequence(PyObject *seq, int64_t cap, const int is_lis VEC v = vec_alloc(alloc_size); if (VEC_IS_ERROR(v)) return vec_error(); +#ifdef Py_GIL_DISABLED + if (is_list) { + // Other threads could be mutating the list concurrently, so use strong references + Py_ssize_t actual = 0; + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(seq) && i < n; i++) { + PyObject *item = PyList_GetItemRef(seq, i); + if (unlikely(item == NULL)) { + // Race condition: list shrank between size read and get item + VEC_DECREF(v); + return vec_error(); + } + ITEM_C_TYPE x = UNBOX_ITEM(item); + Py_DECREF(item); + if (IS_UNBOX_ERROR(x)) { + VEC_DECREF(v); + return vec_error(); + } + v.items[actual++] = x; + } + v.len = actual; + return v; + } + // Tuples are immutable, so fall through to the shared loop below +#endif for (Py_ssize_t i = 0; i < n; i++) { PyObject *item = is_list ? PyList_GET_ITEM(seq, i) : PyTuple_GET_ITEM(seq, i); ITEM_C_TYPE x = UNBOX_ITEM(item); diff --git a/mypyc/lower/registry.py b/mypyc/lower/registry.py index dec6a24b9417a..36262c4a011a3 100644 --- a/mypyc/lower/registry.py +++ b/mypyc/lower/registry.py @@ -26,4 +26,4 @@ def wrapper(f: LF) -> LF: # Import various modules that set up global state. -from mypyc.lower import int_ops, list_ops, misc_ops # noqa: F401 +from mypyc.lower import int_ops, list_ops, misc_ops, vec_ops # noqa: F401 diff --git a/mypyc/lower/vec_ops.py b/mypyc/lower/vec_ops.py new file mode 100644 index 0000000000000..768c8e0073af8 --- /dev/null +++ b/mypyc/lower/vec_ops.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from mypyc.ir.ops import Value +from mypyc.irbuild.ll_builder import LowLevelIRBuilder +from mypyc.irbuild.vec import vec_get_item_unsafe_lower +from mypyc.lower.registry import lower_primitive_op + + +@lower_primitive_op("vec_get_item_unsafe") +def vec_get_item_unsafe(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + base, index = args + return vec_get_item_unsafe_lower(builder, base, index, line, can_borrow=False) + + +@lower_primitive_op("vec_get_item_unsafe_borrow") +def vec_get_item_unsafe_borrow(builder: LowLevelIRBuilder, args: list[Value], line: int) -> Value: + base, index = args + return vec_get_item_unsafe_lower(builder, base, index, line, can_borrow=True) diff --git a/mypyc/primitives/bytes_ops.py b/mypyc/primitives/bytes_ops.py index 2db4879441025..96e8069f317ed 100644 --- a/mypyc/primitives/bytes_ops.py +++ b/mypyc/primitives/bytes_ops.py @@ -64,7 +64,6 @@ return_type=bytes_rprimitive, c_function_name="CPyBytes_Concat", error_kind=ERR_MAGIC, - steals=[True, False], ) # bytes * int diff --git a/mypyc/primitives/librt_random_ops.py b/mypyc/primitives/librt_random_ops.py new file mode 100644 index 0000000000000..6aaee84ecd0d6 --- /dev/null +++ b/mypyc/primitives/librt_random_ops.py @@ -0,0 +1,104 @@ +from mypyc.ir.deps import LIBRT_RANDOM +from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER +from mypyc.ir.rtypes import float_rprimitive, int64_rprimitive, random_rprimitive +from mypyc.primitives.registry import function_op, method_op + +# Random() -- construct with OS entropy +function_op( + name="librt.random.Random", + arg_types=[], + return_type=random_rprimitive, + c_function_name="LibRTRandom_Random_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Random(seed) -- construct with integer seed +function_op( + name="librt.random.Random", + arg_types=[int64_rprimitive], + return_type=random_rprimitive, + c_function_name="LibRTRandom_Random_from_seed_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Random.randint(a, b) -- return random integer in [a, b] +method_op( + name="randint", + arg_types=[random_rprimitive, int64_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_Random_randint_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Random.randrange(stop) -- return random integer in [0, stop) +method_op( + name="randrange", + arg_types=[random_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_Random_randrange1_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Random.randrange(start, stop) -- return random integer in [start, stop) +method_op( + name="randrange", + arg_types=[random_rprimitive, int64_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_Random_randrange2_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Random.random() -- return random float in [0.0, 1.0) +method_op( + name="random", + arg_types=[random_rprimitive], + return_type=float_rprimitive, + c_function_name="LibRTRandom_Random_random_internal", + error_kind=ERR_NEVER, + dependencies=[LIBRT_RANDOM], +) + +# Module-level random() -- return random float using thread-local RNG +function_op( + name="librt.random.random", + arg_types=[], + return_type=float_rprimitive, + c_function_name="LibRTRandom_module_random_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Module-level randrange(stop) -- return random integer using thread-local RNG +function_op( + name="librt.random.randrange", + arg_types=[int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_module_randrange1_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Module-level randrange(start, stop) -- return random integer using thread-local RNG +function_op( + name="librt.random.randrange", + arg_types=[int64_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_module_randrange2_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) + +# Module-level randint(a, b) -- return random integer using thread-local RNG +function_op( + name="librt.random.randint", + arg_types=[int64_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="LibRTRandom_module_randint_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_RANDOM], +) diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 502ab8269e8c4..f3fceb483f968 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -387,3 +387,77 @@ error_kind=ERR_NEVER, dependencies=[LIBRT_STRINGS, STRING_WRITER_EXTRA_OPS], ) + + +# Codepoint classification helpers operating on i32 codepoints +# (typically obtained via ord(s[i])). Negative inputs return False. +function_op( + name="librt.strings.isspace", + arg_types=[int32_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTStrings_IsSpace", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +function_op( + name="librt.strings.isdigit", + arg_types=[int32_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTStrings_IsDigit", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +function_op( + name="librt.strings.isalnum", + arg_types=[int32_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTStrings_IsAlnum", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +function_op( + name="librt.strings.isalpha", + arg_types=[int32_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTStrings_IsAlpha", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +# isidentifier checks XID_Start semantics for a single codepoint, matching +# str.isidentifier() on a 1-character string. The non-ASCII path allocates +# and aborts via CPyError_OutOfMemory on failure, so this stays ERR_NEVER. +function_op( + name="librt.strings.isidentifier", + arg_types=[int32_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTStrings_IsIdentifier", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +# Codepoint case conversion. When the Unicode uppercase/lowercase of a +# codepoint expands to multiple codepoints (e.g. U+00DF uppercases to "SS", +# U+FB01 to "FI"), returns the input unchanged so the signature stays +# i32 -> i32; callers needing full Unicode case conversion should use +# str.upper() / .lower() instead. Negative inputs are returned unchanged. +function_op( + name="librt.strings.toupper", + arg_types=[int32_rprimitive], + return_type=int32_rprimitive, + c_function_name="LibRTStrings_ToUpper", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) + +function_op( + name="librt.strings.tolower", + arg_types=[int32_rprimitive], + return_type=int32_rprimitive, + c_function_name="LibRTStrings_ToLower", + error_kind=ERR_NEVER, + dependencies=[LIBRT_STRINGS], +) diff --git a/mypyc/primitives/librt_threading_ops.py b/mypyc/primitives/librt_threading_ops.py new file mode 100644 index 0000000000000..c060742dc2050 --- /dev/null +++ b/mypyc/primitives/librt_threading_ops.py @@ -0,0 +1,54 @@ +from mypyc.ir.deps import LIBRT_THREADING +from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER +from mypyc.ir.rtypes import bool_rprimitive, lock_rprimitive, none_rprimitive +from mypyc.primitives.registry import function_op, method_op + +# Lock() +function_op( + name="librt.threading.Lock", + arg_types=[], + return_type=lock_rprimitive, + c_function_name="LibRTThreading_Lock_new_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_THREADING], +) + +# Lock.acquire() -- blocking acquire, returns True unless it raises +lock_acquire_op = method_op( + name="acquire", + arg_types=[lock_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTThreading_Lock_acquire_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_THREADING], +) + +# Lock.acquire(blocking) -- acquire with explicit blocking argument +method_op( + name="acquire", + arg_types=[lock_rprimitive, bool_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTThreading_Lock_acquire_blocking_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_THREADING], +) + +# Lock.release() +lock_release_op = method_op( + name="release", + arg_types=[lock_rprimitive], + return_type=none_rprimitive, + c_function_name="LibRTThreading_Lock_release_internal", + error_kind=ERR_MAGIC, + dependencies=[LIBRT_THREADING], +) + +# Lock.locked() +method_op( + name="locked", + arg_types=[lock_rprimitive], + return_type=bool_rprimitive, + c_function_name="LibRTThreading_Lock_locked_internal", + error_kind=ERR_NEVER, + dependencies=[LIBRT_THREADING], +) diff --git a/mypyc/primitives/librt_vecs_ops.py b/mypyc/primitives/librt_vecs_ops.py index e4852d5387069..901c779a2bfd2 100644 --- a/mypyc/primitives/librt_vecs_ops.py +++ b/mypyc/primitives/librt_vecs_ops.py @@ -1,13 +1,15 @@ from mypyc.ir.deps import LIBRT_VECS, VECS_EXTRA_OPS from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER from mypyc.ir.rtypes import ( + RTypeVar, RVec, bit_rprimitive, bytes_rprimitive, + int64_rprimitive, object_rprimitive, uint8_rprimitive, ) -from mypyc.primitives.registry import function_op +from mypyc.primitives.registry import custom_primitive_op, function_op # isinstance(obj, vec) isinstance_vec = function_op( @@ -28,3 +30,22 @@ error_kind=ERR_MAGIC, dependencies=[LIBRT_VECS, VECS_EXTRA_OPS], ) + +# Get vec item, assuming the index is valid (no bounds check) +vec_get_item_unsafe_op = custom_primitive_op( + name="vec_get_item_unsafe", + arg_types=[RVec(RTypeVar(0)), int64_rprimitive], + return_type=RTypeVar(0), + error_kind=ERR_NEVER, + type_params=[RTypeVar(0)], +) + +# Like vec_get_item_unsafe, but the result is a borrowed reference +vec_get_item_unsafe_borrow_op = custom_primitive_op( + name="vec_get_item_unsafe_borrow", + arg_types=[RVec(RTypeVar(0)), int64_rprimitive], + is_borrowed=True, + return_type=RTypeVar(0), + error_kind=ERR_NEVER, + type_params=[RTypeVar(0)], +) diff --git a/mypyc/primitives/list_ops.py b/mypyc/primitives/list_ops.py index efd40469606f3..8af7e9e71df7e 100644 --- a/mypyc/primitives/list_ops.py +++ b/mypyc/primitives/list_ops.py @@ -2,6 +2,7 @@ from __future__ import annotations +from mypyc.common import IS_FREE_THREADED from mypyc.ir.ops import ERR_FALSE, ERR_MAGIC, ERR_NEVER from mypyc.ir.rtypes import ( bit_rprimitive, @@ -108,30 +109,8 @@ priority=2, ) -# list[index] that produces a borrowed result -method_op( - name="__getitem__", - arg_types=[list_rprimitive, int_rprimitive], - return_type=object_rprimitive, - c_function_name="CPyList_GetItemBorrow", - error_kind=ERR_MAGIC, - is_borrowed=True, - priority=3, -) - -# list[index] that produces a borrowed result and index is known to be short -method_op( - name="__getitem__", - arg_types=[list_rprimitive, short_int_rprimitive], - return_type=object_rprimitive, - c_function_name="CPyList_GetItemShortBorrow", - error_kind=ERR_MAGIC, - is_borrowed=True, - priority=4, -) - # Version with native int index -method_op( +list_get_item_int64_op = method_op( name="__getitem__", arg_types=[list_rprimitive, int64_rprimitive], return_type=object_rprimitive, @@ -140,16 +119,39 @@ priority=5, ) -# Version with native int index -method_op( - name="__getitem__", - arg_types=[list_rprimitive, int64_rprimitive], - return_type=object_rprimitive, - c_function_name="CPyList_GetItemInt64Borrow", - is_borrowed=True, - error_kind=ERR_MAGIC, - priority=6, -) +if not IS_FREE_THREADED: + # list[index] that produces a borrowed result + method_op( + name="__getitem__", + arg_types=[list_rprimitive, int_rprimitive], + return_type=object_rprimitive, + c_function_name="CPyList_GetItemBorrow", + error_kind=ERR_MAGIC, + is_borrowed=True, + priority=3, + ) + + # list[index] that produces a borrowed result and index is known to be short + method_op( + name="__getitem__", + arg_types=[list_rprimitive, short_int_rprimitive], + return_type=object_rprimitive, + c_function_name="CPyList_GetItemShortBorrow", + error_kind=ERR_MAGIC, + is_borrowed=True, + priority=4, + ) + + # Version with native int index + method_op( + name="__getitem__", + arg_types=[list_rprimitive, int64_rprimitive], + return_type=object_rprimitive, + c_function_name="CPyList_GetItemInt64Borrow", + is_borrowed=True, + error_kind=ERR_MAGIC, + priority=6, + ) # This is unsafe because it assumes that the index is a non-negative integer # that is in-bounds for the list. diff --git a/mypyc/primitives/registry.py b/mypyc/primitives/registry.py index c04b4ff65a757..060af0f88020c 100644 --- a/mypyc/primitives/registry.py +++ b/mypyc/primitives/registry.py @@ -41,7 +41,7 @@ from mypyc.ir.deps import Dependency from mypyc.ir.ops import PrimitiveDescription, StealsDescription -from mypyc.ir.rtypes import RType +from mypyc.ir.rtypes import RType, RTypeVar # Error kind for functions that return negative integer on exception. This # is only used for primitives. We translate it away during IR building. @@ -154,6 +154,7 @@ def method_op( is_pure=is_pure, experimental=experimental, dependencies=dependencies, + type_params=None, ) ops.append(desc) return desc @@ -204,6 +205,7 @@ def function_op( is_pure=False, experimental=experimental, dependencies=dependencies, + type_params=None, ) ops.append(desc) return desc @@ -253,6 +255,7 @@ def binary_op( is_pure=False, experimental=False, dependencies=dependencies, + type_params=None, ) ops.append(desc) return desc @@ -313,6 +316,7 @@ def custom_primitive_op( is_pure: bool = False, experimental: bool = False, dependencies: list[Dependency] | None = None, + type_params: list[RTypeVar] | None = None, ) -> PrimitiveDescription: """Define a primitive op that can't be automatically generated based on the AST. @@ -336,6 +340,7 @@ def custom_primitive_op( is_pure=is_pure, experimental=experimental, dependencies=dependencies, + type_params=type_params, ) @@ -380,6 +385,7 @@ def unary_op( is_pure=is_pure, experimental=False, dependencies=dependencies, + type_params=None, ) ops.append(desc) return desc @@ -403,7 +409,9 @@ def load_global_op(name: str, type: RType, src: str) -> LoadAddressDescription: import mypyc.primitives.dict_ops import mypyc.primitives.float_ops import mypyc.primitives.int_ops +import mypyc.primitives.librt_random_ops import mypyc.primitives.librt_strings_ops +import mypyc.primitives.librt_threading_ops import mypyc.primitives.librt_time_ops import mypyc.primitives.librt_vecs_ops import mypyc.primitives.list_ops diff --git a/mypyc/rt_expandtype.py b/mypyc/rt_expandtype.py new file mode 100644 index 0000000000000..8537e6777ccb5 --- /dev/null +++ b/mypyc/rt_expandtype.py @@ -0,0 +1,32 @@ +from mypyc.ir.rtypes import ( + RArray, + RInstance, + RPrimitive, + RStruct, + RTuple, + RType, + RTypeVar, + RUnion, + RVec, + RVoid, +) + + +def expand_rtype(typ: RType, type_args: list[RType]) -> RType: + if isinstance(typ, (RPrimitive, RInstance, RVoid)): + # Atomic types can't contain type variables + return typ + elif isinstance(typ, RTypeVar): + return type_args[typ.id] + elif isinstance(typ, RVec): + return RVec(expand_rtype(typ.item_type, type_args)) + elif isinstance(typ, RUnion): + return RUnion([expand_rtype(item, type_args) for item in typ.items]) + elif isinstance(typ, RTuple): + return RTuple([expand_rtype(item, type_args) for item in typ.types]) + elif isinstance(typ, RStruct): + assert False, "Generic RStruct type not supported" + elif isinstance(typ, RArray): + assert False, "Generic RArray type not supported" + else: + assert False, r"Unexpected type {typ!r}" diff --git a/mypyc/test-data/exceptions.test b/mypyc/test-data/exceptions.test index c271c9ae63d5e..b8d8b4727363c 100644 --- a/mypyc/test-data/exceptions.test +++ b/mypyc/test-data/exceptions.test @@ -98,7 +98,7 @@ L6: r5 = :: int return r5 -[case testListSum] +[case testListSum_withgil] from typing import List def sum(a: List[int], l: int) -> int: sum = 0 @@ -553,7 +553,7 @@ L3: r3 = :: i64 return r3 -[case testExceptionWithNativeAttributeGetAndSet] +[case testExceptionWithNativeAttributeGetAndSet_withgil] class C: def __init__(self, x: int) -> None: self.x = x @@ -578,6 +578,32 @@ L0: c.x = r1 return 1 +[case testExceptionWithNativeAttributeGetAndSet_nogil] +class C: + def __init__(self, x: int) -> None: + self.x = x + +def foo(c: C, x: int) -> None: + c.x = x - c.x +[out] +def C.__init__(self, x): + self :: __main__.C + x :: int +L0: + inc_ref x :: int + self.x = x + return 1 +def foo(c, x): + c :: __main__.C + x, r0, r1 :: int + r2 :: bool +L0: + r0 = c.x + r1 = CPyTagged_Subtract(x, r0) + dec_ref r0 :: int + c.x = r1 + return 1 + [case testExceptionWithOverlappingFloatErrorValue] def f() -> float: return 0.0 diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index a9d7ee2357b68..c6c231f0386be 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1870,7 +1870,7 @@ L0: r5 = a.f(12, 6, r4) return 1 -[case testListComprehension] +[case testListComprehension_withgil] from typing import List def f() -> List[int]: @@ -1931,7 +1931,7 @@ L7: L8: return r0 -[case testDictComprehension] +[case testDictComprehension_withgil] from typing import Dict def f() -> Dict[int, int]: return {x: x*x for x in [1,2,3] if x != 2 if x != 3} @@ -1993,7 +1993,7 @@ L7: L8: return r0 -[case testLoopsMultipleAssign] +[case testLoopsMultipleAssign_withgil] from typing import List, Tuple def f(l: List[Tuple[int, int, int]]) -> List[int]: for x, y, z in l: @@ -2063,7 +2063,7 @@ L7: L8: return r10 -[case testProperty] +[case testProperty_withgil] class PropertyHolder: @property def value(self) -> int: @@ -2114,7 +2114,7 @@ L0: r1 = CPyTagged_Multiply(4, r0) return r1 -[case testNativeIndex] +[case testNativeIndex_withgil] from typing import List class A: def __getitem__(self, index: int) -> int: pass diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index d13bd956e1259..8eaa63e4583b5 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -26,7 +26,7 @@ L0: a.x = 2; r0 = is_error return 1 -[case testUserClassInList] +[case testUserClassInList_withgil] class C: x: int @@ -103,7 +103,7 @@ L0: r0 = a.n return r0 -[case testOptionalMember] +[case testOptionalMember_withgil] from typing import Optional class Node: next: Optional[Node] @@ -1135,7 +1135,7 @@ class Ok2: __deletable__ = ['x'] x: int -[case testInvalidDeletableAttribute] +[case testDeleteNonDeletableAttribute] class NotDeletable: __deletable__ = ['x'] x: int @@ -1146,6 +1146,7 @@ def g(o: NotDeletable) -> None: del o.y # E: "y" cannot be deleted \ # N: Using "__deletable__ = ['']" in the class body enables "del obj." +[case testInvalidDeletableAttribute] class Base: x: int @@ -1238,7 +1239,7 @@ L0: __mypyc_self__.s = r0 return 1 -[case testBorrowAttribute] +[case testBorrowAttribute_withgil] def f(d: D) -> int: return d.c.x @@ -1257,6 +1258,64 @@ L0: keep_alive d return r1 +[case testCannotBorrowAttribute_nogil] +def f(d: D) -> int: + return d.c.x + +class C: + x: int +class D: + c: C +[out] +def f(d): + d :: __main__.D + r0 :: __main__.C + r1 :: int +L0: + r0 = d.c + r1 = r0.x + return r1 + +[case testCanBorrowFinalAttribute_nogil] +from typing import Final + +# On free-threaded builds native attribute reads are not borrowed (a concurrent +# store could free the old value), EXCEPT Final attributes, which can't be rebound. +# Here the intermediate 'd.c' is borrowed because 'c' is Final; contrast with +# testCannotBorrowAttribute_nogil, where the non-Final 'c' is read owned. The +# borrow decision uses the same ClassIR.is_final_attr predicate as codegen. +def f(d: D) -> int: + return d.c.xf + +class C: + def __init__(self, xf: int) -> None: + self.xf: Final = xf +class D: + def __init__(self, c: C) -> None: + self.c: Final = c +[out] +def f(d): + d :: __main__.D + r0 :: __main__.C + r1 :: int +L0: + r0 = borrow d.c + r1 = r0.xf + keep_alive d + return r1 +def C.__init__(self, xf): + self :: __main__.C + xf :: int +L0: + self.xf = xf + return 1 +def D.__init__(self, c): + self :: __main__.D + c :: __main__.C +L0: + self.c = c + return 1 + [case testNoBorrowOverPropertyAccess] class C: d: D @@ -1286,7 +1345,7 @@ L0: r2 = r1.x return r2 -[case testBorrowResultOfCustomGetItemInIfStatement] +[case testBorrowResultOfCustomGetItemInIfStatement_withgil] from typing import List class C: diff --git a/mypyc/test-data/irbuild-final.test b/mypyc/test-data/irbuild-final.test new file mode 100644 index 0000000000000..0ede988a43206 --- /dev/null +++ b/mypyc/test-data/irbuild-final.test @@ -0,0 +1,252 @@ +[case testFinalAttrBorrowed] +from typing import Final + +class C: + def __init__(self, x: str) -> None: + self.f: Final = x + self.x = x + +def f(s: str) -> None: pass + +def calls(c: C) -> None: + f(c.f) + f(c.x) + c.f.startswith("a") + +def ret(c: C) -> str: + return c.f +[out] +def C.__init__(self, x): + self :: __main__.C + x :: str +L0: + self.f = x + self.x = x + return 1 +def f(s): + s :: str +L0: + return 1 +def calls(c): + c :: __main__.C + r0 :: str + r1 :: None + r2 :: str + r3 :: None + r4, r5 :: str + r6 :: i32 + r7 :: bool +L0: + r0 = borrow c.f + r1 = f(r0) + keep_alive c + r2 = c.x + r3 = f(r2) + r4 = borrow c.f + r5 = 'a' + r6 = CPyStr_Startswith(r4, r5) + r7 = truncate r6: i32 to builtins.bool + keep_alive c + return 1 +def ret(c): + c :: __main__.C + r0 :: str +L0: + r0 = c.f + return r0 + +[case testInheritedFinalAttrBorrowed] +from typing import Final + +class Base: + x: str + + def __init__(self, s: str) -> None: + self.s: Final = s + +class Deriv(Base): + def __init__(self, n: int) -> None: + self.n: Final = n + +def f(b: Base) -> str: + return b.s + "a" + +def g(d: Deriv) -> str: + return d.s * d.n + +def h(d: Deriv) -> str: + return d.x + "a" +[out] +def Base.__init__(self, s): + self :: __main__.Base + s :: str +L0: + self.s = s + return 1 +def Deriv.__init__(self, n): + self :: __main__.Deriv + n :: int +L0: + self.n = n + return 1 +def f(b): + b :: __main__.Base + r0, r1, r2 :: str +L0: + r0 = borrow b.s + r1 = 'a' + r2 = PyUnicode_Concat(r0, r1) + keep_alive b + return r2 +def g(d): + d :: __main__.Deriv + r0 :: str + r1 :: int + r2 :: str +L0: + r0 = borrow d.s + r1 = borrow d.n + r2 = CPyStr_Multiply(r0, r1) + keep_alive d, d + return r2 +def h(d): + d :: __main__.Deriv + r0, r1, r2 :: str +L0: + r0 = d.x + r1 = 'a' + r2 = PyUnicode_Concat(r0, r1) + return r2 + +[case testFinalAttrExpressionKinds] +from typing import Final + +class C: + def __init__(self, s: str, n: int, l: list[str]) -> None: + self.f: Final = s + self.n: Final = n + self.l: Final = l + self.s = s + +def f() -> C: + return C("", 0, []) + +def assign(c: C) -> None: + a = c.f + c.s = c.f + +def indexing(c: C) -> str: + return c.l[c.n] + +def call_eq(c: C) -> bool: + return f().f == c.f +[out] +def C.__init__(self, s, n, l): + self :: __main__.C + s :: str + n :: int + l :: list +L0: + self.f = s + self.n = n + self.l = l + self.s = s + return 1 +def f(): + r0 :: str + r1 :: list + r2 :: __main__.C +L0: + r0 = '' + r1 = PyList_New(0) + r2 = C(r0, 0, r1) + return r2 +def assign(c): + c :: __main__.C + r0, a, r1 :: str + r2 :: bool +L0: + r0 = c.f + a = r0 + r1 = c.f + c.s = r1; r2 = is_error + return 1 +def indexing(c): + c :: __main__.C + r0 :: list + r1 :: int + r2 :: object + r3 :: str +L0: + r0 = borrow c.l + r1 = borrow c.n + r2 = CPyList_GetItem(r0, r1) + r3 = cast(str, r2) + keep_alive c, c + return r3 +def call_eq(c): + c, r0 :: __main__.C + r1, r2 :: str + r3 :: bool +L0: + r0 = f() + r1 = borrow r0.f + r2 = borrow c.f + r3 = CPyStr_Equal(r1, r2) + keep_alive r0, c + return r3 + +[case testFinalAttrExpressionKinds2_withgil] +from typing import Final + +class C: + def __init__(self, d: D) -> None: + self.d: Final = d + +class D: + def __init__(self, s: str) -> None: + self.f: Final = s + +def f(c: C) -> str: + return c.d.f + "a" + +def g(a: list[D], n: int) -> str: + return a[n].f + "a" +[out] +def C.__init__(self, d): + self :: __main__.C + d :: __main__.D +L0: + self.d = d + return 1 +def D.__init__(self, s): + self :: __main__.D + s :: str +L0: + self.f = s + return 1 +def f(c): + c :: __main__.C + r0 :: __main__.D + r1, r2, r3 :: str +L0: + r0 = borrow c.d + r1 = borrow r0.f + r2 = 'a' + r3 = PyUnicode_Concat(r1, r2) + keep_alive c, r0 + return r3 +def g(a, n): + a :: list + n :: int + r0 :: object + r1 :: __main__.D + r2, r3, r4 :: str +L0: + r0 = CPyList_GetItemBorrow(a, n) + r1 = borrow cast(__main__.D, r0) + r2 = r1.f + keep_alive a, n, r0 + r3 = 'a' + r4 = PyUnicode_Concat(r2, r3) + return r4 diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 9ec29182e89b6..0d4dad4f97e23 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -40,7 +40,7 @@ L0: y = r3 return 1 -[case testGenericAttrAndTypeApplication] +[case testGenericAttrAndTypeApplication_withgil] from typing import TypeVar, Generic T = TypeVar('T') class C(Generic[T]): diff --git a/mypyc/test-data/irbuild-glue-methods.test b/mypyc/test-data/irbuild-glue-methods.test index 87b3bf7fee588..bdbdb7e2afa68 100644 --- a/mypyc/test-data/irbuild-glue-methods.test +++ b/mypyc/test-data/irbuild-glue-methods.test @@ -91,7 +91,7 @@ L0: r0 = x.foo(y) return r0 -[case testPropertyDerivedGen] +[case testPropertyDerivedGen_withgil] from typing import Callable class BaseProperty: @property diff --git a/mypyc/test-data/irbuild-i64.test b/mypyc/test-data/irbuild-i64.test index 0fcadefd46c27..4cea7894052aa 100644 --- a/mypyc/test-data/irbuild-i64.test +++ b/mypyc/test-data/irbuild-i64.test @@ -1032,7 +1032,7 @@ L4: y = r3 return y -[case testBorrowOverI64Arithmetic] +[case testBorrowOverI64Arithmetic_withgil] from mypy_extensions import i64 def add_simple(c: C) -> i64: @@ -1084,7 +1084,7 @@ L0: keep_alive d, d return r4 -[case testBorrowOverI64Bitwise] +[case testBorrowOverI64Bitwise_withgil] from mypy_extensions import i64 def bitwise_simple(c: C) -> i64: @@ -1136,7 +1136,7 @@ L0: keep_alive d, d return r4 -[case testBorrowOverI64ListGetItem1] +[case testBorrowOverI64ListGetItem1_withgil] from mypy_extensions import i64 def f(n: i64) -> str: @@ -1168,7 +1168,38 @@ L0: keep_alive a, n, r3 return r5 -[case testBorrowOverI64ListGetItem2] +[case testBorrowOverI64ListGetItem1_nogil] +from mypy_extensions import i64 + +def f(n: i64) -> str: + a = [C()] + return a[n].s + +class C: + s: str +[out] +def f(n): + n :: i64 + r0 :: __main__.C + r1 :: list + r2 :: ptr + a :: list + r3 :: object + r4 :: __main__.C + r5 :: str +L0: + r0 = C() + r1 = PyList_New(1) + r2 = list_items r1 + buf_init_item r2, 0, r0 + keep_alive r1 + a = r1 + r3 = CPyList_GetItemInt64(a, n) + r4 = cast(__main__.C, r3) + r5 = r4.s + return r5 + +[case testBorrowOverI64ListGetItem2_withgil] from typing import List from mypy_extensions import i64 @@ -1194,6 +1225,31 @@ L1: L2: return 0 +[case testBorrowOverI64ListGetItem2_nogil] +from typing import List +from mypy_extensions import i64 + +def f(a: List[i64], n: i64) -> bool: + if a[n] == 0: + return True + return False +[out] +def f(a, n): + a :: list + n :: i64 + r0 :: object + r1 :: i64 + r2 :: bit +L0: + r0 = CPyList_GetItemInt64(a, n) + r1 = unbox(i64, r0) + r2 = r1 == 0 + if r2 goto L1 else goto L2 :: bool +L1: + return 1 +L2: + return 0 + [case testCoerceShortIntToI64] from mypy_extensions import i64 from typing import List @@ -2014,7 +2070,7 @@ L4: r6 = CPyFloat_FromTagged(r3) return r6 -[case testI64IsinstanceNarrowing] +[case testI64IsinstanceNarrowing_withgil] from typing import Union from mypy_extensions import i64 diff --git a/mypyc/test-data/irbuild-isinstance.test b/mypyc/test-data/irbuild-isinstance.test index 36a9300350bd3..216a52ff2a25c 100644 --- a/mypyc/test-data/irbuild-isinstance.test +++ b/mypyc/test-data/irbuild-isinstance.test @@ -48,7 +48,7 @@ L2: L3: return r1 -[case testBorrowSpecialCaseWithIsinstance] +[case testBorrowSpecialCaseWithIsinstance_withgil] class C: s: str diff --git a/mypyc/test-data/irbuild-librt-random.test b/mypyc/test-data/irbuild-librt-random.test new file mode 100644 index 0000000000000..9215c13c88d6e --- /dev/null +++ b/mypyc/test-data/irbuild-librt-random.test @@ -0,0 +1,119 @@ +[case testLibrtRandomConstructor_64bit] +from librt.random import Random + +def make_random() -> Random: + return Random() +[out] +def make_random(): + r0 :: librt.random.Random +L0: + r0 = LibRTRandom_Random_internal() + return r0 + +[case testLibrtRandomConstructorWithSeed_64bit] +from librt.random import Random +from mypy_extensions import i64 + +def make_random_seeded(n: i64) -> Random: + return Random(n) +[out] +def make_random_seeded(n): + n :: i64 + r0 :: librt.random.Random +L0: + r0 = LibRTRandom_Random_from_seed_internal(n) + return r0 + +[case testLibrtRandomRandrange_64bit] +from librt.random import Random +from mypy_extensions import i64 + +def randrange1(r: Random, stop: i64) -> i64: + return r.randrange(stop) +def randrange2(r: Random, start: i64, stop: i64) -> i64: + return r.randrange(start, stop) +[out] +def randrange1(r, stop): + r :: librt.random.Random + stop, r0 :: i64 +L0: + r0 = LibRTRandom_Random_randrange1_internal(r, stop) + return r0 +def randrange2(r, start, stop): + r :: librt.random.Random + start, stop, r0 :: i64 +L0: + r0 = LibRTRandom_Random_randrange2_internal(r, start, stop) + return r0 + +[case testLibrtRandomRandint_64bit] +from librt.random import Random +from mypy_extensions import i64 + +def randint(r: Random, a: i64, b: i64) -> i64: + return r.randint(a, b) +[out] +def randint(r, a, b): + r :: librt.random.Random + a, b, r0 :: i64 +L0: + r0 = LibRTRandom_Random_randint_internal(r, a, b) + return r0 + +[case testLibrtRandomRandom_64bit] +from librt.random import Random + +def rand(r: Random) -> float: + return r.random() +[out] +def rand(r): + r :: librt.random.Random + r0 :: float +L0: + r0 = LibRTRandom_Random_random_internal(r) + return r0 + +[case testLibrtRandomModuleRandom_64bit] +from librt.random import random + +def module_random() -> float: + return random() +[out] +def module_random(): + r0 :: float +L0: + r0 = LibRTRandom_module_random_internal() + return r0 + +[case testLibrtRandomModuleRandint_64bit] +from librt.random import randint +from mypy_extensions import i64 + +def module_randint(a: i64, b: i64) -> i64: + return randint(a, b) +[out] +def module_randint(a, b): + a, b, r0 :: i64 +L0: + r0 = LibRTRandom_module_randint_internal(a, b) + return r0 + +[case testLibrtRandomModuleRandrange_64bit] +from librt.random import randrange +from mypy_extensions import i64 + +def module_randrange1(stop: i64) -> i64: + return randrange(stop) +def module_randrange2(start: i64, stop: i64) -> i64: + return randrange(start, stop) +[out] +def module_randrange1(stop): + stop, r0 :: i64 +L0: + r0 = LibRTRandom_module_randrange1_internal(stop) + return r0 +def module_randrange2(start, stop): + start, stop, r0 :: i64 +L0: + r0 = LibRTRandom_module_randrange2_internal(start, stop) + return r0 diff --git a/mypyc/test-data/irbuild-librt-strings.test b/mypyc/test-data/irbuild-librt-strings.test index 460a109d1d5ac..83523cb2468e0 100644 --- a/mypyc/test-data/irbuild-librt-strings.test +++ b/mypyc/test-data/irbuild-librt-strings.test @@ -270,3 +270,160 @@ L1: L2: r3 = CPyStringWriter_GetItem(s, r0) return r3 + +[case testLibrtStringsIsSpaceIR] +from librt.strings import isspace +from mypy_extensions import i32 + +def is_ws(c: i32) -> bool: + return isspace(c) +[out] +def is_ws(c): + c :: i32 + r0 :: bool +L0: + r0 = LibRTStrings_IsSpace(c) + return r0 + +[case testLibrtStringsIsSpaceFromStrIndexIR_64bit] +from librt.strings import isspace + +def is_ws_at(s: str, i: int) -> bool: + return isspace(ord(s[i])) +[out] +def is_ws_at(s, i): + s :: str + i :: int + r0 :: native_int + r1 :: bit + r2, r3 :: i64 + r4 :: ptr + r5 :: c_ptr + r6, r7 :: i64 + r8, r9 :: bool + r10 :: short_int + r11, r12 :: bit + r13 :: native_int + r14, r15 :: i32 + r16 :: bool +L0: + r0 = i & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = i >> 1 + r3 = r2 + goto L3 +L2: + r4 = i ^ 1 + r5 = r4 + r6 = CPyLong_AsInt64(r5) + r3 = r6 + keep_alive i +L3: + r7 = CPyStr_AdjustIndex(s, r3) + r8 = CPyStr_RangeCheck(s, r7) + if r8 goto L5 else goto L4 :: bool +L4: + r9 = raise IndexError('index out of range') + unreachable +L5: + r10 = CPyStr_GetItemUnsafeAsInt(s, r7) + r11 = r10 < 4294967296 :: signed + if r11 goto L6 else goto L8 :: bool +L6: + r12 = r10 >= -4294967296 :: signed + if r12 goto L7 else goto L8 :: bool +L7: + r13 = r10 >> 1 + r14 = truncate r13: native_int to i32 + r15 = r14 + goto L9 +L8: + CPyInt32_Overflow() + unreachable +L9: + r16 = LibRTStrings_IsSpace(r15) + return r16 + +[case testLibrtStringsIsDigitIR] +from librt.strings import isdigit +from mypy_extensions import i32 + +def is_d(c: i32) -> bool: + return isdigit(c) +[out] +def is_d(c): + c :: i32 + r0 :: bool +L0: + r0 = LibRTStrings_IsDigit(c) + return r0 + +[case testLibrtStringsIsAlnumIR] +from librt.strings import isalnum +from mypy_extensions import i32 + +def is_an(c: i32) -> bool: + return isalnum(c) +[out] +def is_an(c): + c :: i32 + r0 :: bool +L0: + r0 = LibRTStrings_IsAlnum(c) + return r0 + +[case testLibrtStringsIsAlphaIR] +from librt.strings import isalpha +from mypy_extensions import i32 + +def is_a(c: i32) -> bool: + return isalpha(c) +[out] +def is_a(c): + c :: i32 + r0 :: bool +L0: + r0 = LibRTStrings_IsAlpha(c) + return r0 + +[case testLibrtStringsIsIdentifierIR] +from librt.strings import isidentifier +from mypy_extensions import i32 + +def is_id(c: i32) -> bool: + return isidentifier(c) +[out] +def is_id(c): + c :: i32 + r0 :: bool +L0: + r0 = LibRTStrings_IsIdentifier(c) + return r0 + +[case testLibrtStringsToUpperIR] +from librt.strings import toupper +from mypy_extensions import i32 + +def up(c: i32) -> i32: + return toupper(c) +[out] +def up(c): + c, r0 :: i32 +L0: + r0 = LibRTStrings_ToUpper(c) + return r0 + +[case testLibrtStringsToLowerIR] +from librt.strings import tolower +from mypy_extensions import i32 + +def lo(c: i32) -> i32: + return tolower(c) +[out] +def lo(c): + c, r0 :: i32 +L0: + r0 = LibRTStrings_ToLower(c) + return r0 diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index f6cdbcaf3a96e..e70d81c96bf7a 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -26,7 +26,7 @@ L0: r1 = cast(list, r0) return r1 -[case testListOfListGet2] +[case testListOfListGet2_withgil] from typing import List def f(x: List[List[int]]) -> int: return x[0][1] @@ -45,6 +45,24 @@ L0: keep_alive x, r0 return r3 +[case testListOfListGet2_nogil] +from typing import List +def f(x: List[List[int]]) -> int: + return x[0][1] +[out] +def f(x): + x :: list + r0 :: object + r1 :: list + r2 :: object + r3 :: int +L0: + r0 = CPyList_GetItemShort(x, 0) + r1 = cast(list, r0) + r2 = CPyList_GetItemShort(r1, 2) + r3 = unbox(int, r2) + return r3 + [case testListSet] from typing import List def f(x: List[int]) -> None: @@ -361,7 +379,7 @@ L0: r2 = r1 >= 0 :: signed return 1 -[case testListBuiltFromGenerator] +[case testListBuiltFromGenerator_withgil] from typing import List def f(source: List[int]) -> None: a = list(x + 1 for x in source) @@ -430,7 +448,7 @@ L8: b = r11 return 1 -[case testGeneratorNext] +[case testGeneratorNext_withgil] from typing import List, Optional def test(x: List[int]) -> None: @@ -471,7 +489,7 @@ L5: res = r6 return 1 -[case testSimplifyListUnion] +[case testSimplifyListUnion_withgil] from typing import List, Union, Optional def narrow(a: Union[List[str], List[bytes], int]) -> int: @@ -905,7 +923,7 @@ L10: a = r3 return 1 -[case testListBuiltFromStars] +[case testListBuiltFromStars_withgil] from typing import Final abc: Final = "abc" diff --git a/mypyc/test-data/irbuild-optional.test b/mypyc/test-data/irbuild-optional.test index fbf7cb148b089..252f9489dc57f 100644 --- a/mypyc/test-data/irbuild-optional.test +++ b/mypyc/test-data/irbuild-optional.test @@ -237,7 +237,7 @@ L3: L4: return 1 -[case testUnionType] +[case testUnionType_withgil] from typing import Union class A: diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index 00bcff68d4ff5..ac01c58401118 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -53,7 +53,7 @@ L0: r0 = PySet_New(l) return r0 -[case testNewSetFromIterable2] +[case testNewSetFromIterable2_withgil] def f(x: int) -> int: return x @@ -273,7 +273,7 @@ L4: e = r0 return 1 -[case testNewSetFromIterable3] +[case testNewSetFromIterable3_withgil] def f1(x: int) -> int: return x diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index df7eb5fc7449a..8c5c95c8223ec 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -218,7 +218,7 @@ L5: L6: return 1 -[case testForList] +[case testForList_withgil] from typing import List def f(ls: List[int]) -> int: @@ -255,6 +255,43 @@ L3: L4: return y +[case testForList_nogil] +from typing import List + +def f(ls: List[int]) -> int: + y = 0 + for x in ls: + y = y + x + return y +[out] +def f(ls): + ls :: list + y :: int + r0, r1 :: native_int + r2 :: bit + r3 :: object + r4, x, r5 :: int + r6 :: native_int +L0: + y = 0 + r0 = 0 +L1: + r1 = var_object_size ls + r2 = r0 < r1 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = CPyList_GetItemInt64(ls, r0) + r4 = unbox(int, r3) + x = r4 + r5 = CPyTagged_Add(y, x) + y = r5 +L3: + r6 = r0 + 1 + r0 = r6 + goto L1 +L4: + return y + [case testForDictBasic] from typing import Dict @@ -575,7 +612,7 @@ L0: z = r6 return 1 -[case testMultipleAssignmentUnpackFromSequence] +[case testMultipleAssignmentUnpackFromSequence_withgil] from typing import List, Tuple def f(l: List[int], t: Tuple[int, ...]) -> None: @@ -612,6 +649,43 @@ L0: y = r9 return 1 +[case testMultipleAssignmentUnpackFromSequence_nogil] +from typing import List, Tuple + +def f(l: List[int], t: Tuple[int, ...]) -> None: + x: object + y: int + x, y = l + x, y = t +[out] +def f(l, t): + l :: list + t :: tuple + r0 :: i32 + r1 :: bit + r2, r3, x :: object + r4, y :: int + r5 :: i32 + r6 :: bit + r7, r8 :: object + r9 :: int +L0: + r0 = CPySequence_CheckUnpackCount(l, 2) + r1 = r0 >= 0 :: signed + r2 = CPyList_GetItemInt64(l, 0) + r3 = CPyList_GetItemInt64(l, 1) + x = r2 + r4 = unbox(int, r3) + y = r4 + r5 = CPySequence_CheckUnpackCount(t, 2) + r6 = r5 >= 0 :: signed + r7 = CPySequenceTuple_GetItemUnsafe(t, 0) + r8 = CPySequenceTuple_GetItemUnsafe(t, 1) + x = r7 + r9 = unbox(int, r8) + y = r9 + return 1 + [case testAssert] from typing import Optional @@ -862,7 +936,7 @@ L0: r6 = r5 >= 0 :: signed return 1 -[case testForEnumerate] +[case testForEnumerate_withgil] from typing import List, Iterable def f(a: List[int]) -> None: @@ -930,7 +1004,7 @@ L4: L5: return 1 -[case testForZip] +[case testForZip_withgil] from typing import List, Iterable, Sequence def f(a: List[int], b: Sequence[bool]) -> None: diff --git a/mypyc/test-data/irbuild-str.test b/mypyc/test-data/irbuild-str.test index 81cd5bd34c046..b16057f645ba7 100644 --- a/mypyc/test-data/irbuild-str.test +++ b/mypyc/test-data/irbuild-str.test @@ -1025,3 +1025,153 @@ def is_digit(x): L0: r0 = CPyStr_IsDigit(x) return r0 + +[case testStrIndexEqLiteral_64bit] +def is_comma(s: str, i: int) -> bool: + return s[i] == "," +def is_comma_swapped(s: str, i: int) -> bool: + return "," == s[i] +def is_comma_ne(s: str, i: int) -> bool: + return s[i] != "," +[out] +def is_comma(s, i): + s :: str + i :: int + r0 :: native_int + r1 :: bit + r2, r3 :: i64 + r4 :: ptr + r5 :: c_ptr + r6, r7 :: i64 + r8, r9 :: bool + r10 :: short_int + r11 :: bit +L0: + r0 = i & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = i >> 1 + r3 = r2 + goto L3 +L2: + r4 = i ^ 1 + r5 = r4 + r6 = CPyLong_AsInt64(r5) + r3 = r6 + keep_alive i +L3: + r7 = CPyStr_AdjustIndex(s, r3) + r8 = CPyStr_RangeCheck(s, r7) + if r8 goto L5 else goto L4 :: bool +L4: + r9 = raise IndexError('index out of range') + unreachable +L5: + r10 = CPyStr_GetItemUnsafeAsInt(s, r7) + r11 = int_eq r10, 88 + return r11 +def is_comma_swapped(s, i): + s :: str + i :: int + r0 :: native_int + r1 :: bit + r2, r3 :: i64 + r4 :: ptr + r5 :: c_ptr + r6, r7 :: i64 + r8, r9 :: bool + r10 :: short_int + r11 :: bit +L0: + r0 = i & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = i >> 1 + r3 = r2 + goto L3 +L2: + r4 = i ^ 1 + r5 = r4 + r6 = CPyLong_AsInt64(r5) + r3 = r6 + keep_alive i +L3: + r7 = CPyStr_AdjustIndex(s, r3) + r8 = CPyStr_RangeCheck(s, r7) + if r8 goto L5 else goto L4 :: bool +L4: + r9 = raise IndexError('index out of range') + unreachable +L5: + r10 = CPyStr_GetItemUnsafeAsInt(s, r7) + r11 = int_eq r10, 88 + return r11 +def is_comma_ne(s, i): + s :: str + i :: int + r0 :: native_int + r1 :: bit + r2, r3 :: i64 + r4 :: ptr + r5 :: c_ptr + r6, r7 :: i64 + r8, r9 :: bool + r10 :: short_int + r11 :: bit +L0: + r0 = i & 1 + r1 = r0 == 0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = i >> 1 + r3 = r2 + goto L3 +L2: + r4 = i ^ 1 + r5 = r4 + r6 = CPyLong_AsInt64(r5) + r3 = r6 + keep_alive i +L3: + r7 = CPyStr_AdjustIndex(s, r3) + r8 = CPyStr_RangeCheck(s, r7) + if r8 goto L5 else goto L4 :: bool +L4: + r9 = raise IndexError('index out of range') + unreachable +L5: + r10 = CPyStr_GetItemUnsafeAsInt(s, r7) + r11 = int_ne r10, 88 + return r11 + +[case testStrIndexEqLiteralNoSpecialize] +def two_char_literal(s: str, i: int) -> bool: + # Multi-char literals don't match the specialization; falls through to + # the generic str equality path. + return s[i] == "ab" +def empty_literal(s: str, i: int) -> bool: + # Empty string literals also fall through; the generic path returns False. + return s[i] == "" +[out] +def two_char_literal(s, i): + s :: str + i :: int + r0, r1 :: str + r2 :: bool +L0: + r0 = CPyStr_GetItem(s, i) + r1 = 'ab' + r2 = CPyStr_EqualLiteral(r0, r1, 2) + return r2 +def empty_literal(s, i): + s :: str + i :: int + r0, r1 :: str + r2 :: bool +L0: + r0 = CPyStr_GetItem(s, i) + r1 = '' + r2 = CPyStr_EqualLiteral(r0, r1, 0) + return r2 diff --git a/mypyc/test-data/irbuild-threading.test b/mypyc/test-data/irbuild-threading.test new file mode 100644 index 0000000000000..652ccedcb9d75 --- /dev/null +++ b/mypyc/test-data/irbuild-threading.test @@ -0,0 +1,115 @@ +[case testLockBasics] +from librt.threading import Lock + +def lock_create() -> Lock: + return Lock() + +def lock_acquire(lk: Lock) -> bool: + return lk.acquire() + +def lock_release(lk: Lock) -> None: + lk.release() + +def lock_locked(lk: Lock) -> bool: + return lk.locked() +[out] +def lock_create(): + r0 :: librt.threading.Lock +L0: + r0 = LibRTThreading_Lock_new_internal() + return r0 +def lock_acquire(lk): + lk :: librt.threading.Lock + r0 :: bool +L0: + r0 = LibRTThreading_Lock_acquire_internal(lk) + return r0 +def lock_release(lk): + lk :: librt.threading.Lock + r0 :: None +L0: + r0 = LibRTThreading_Lock_release_internal(lk) + return 1 +def lock_locked(lk): + lk :: librt.threading.Lock + r0 :: bool +L0: + r0 = LibRTThreading_Lock_locked_internal(lk) + return r0 + +[case testLockAcquireRelease] +from librt.threading import Lock + +def acquire_release() -> None: + lk = Lock() + lk.acquire() + lk.release() +[out] +def acquire_release(): + r0, lk :: librt.threading.Lock + r1 :: bool + r2 :: None +L0: + r0 = LibRTThreading_Lock_new_internal() + lk = r0 + r1 = LibRTThreading_Lock_acquire_internal(lk) + r2 = LibRTThreading_Lock_release_internal(lk) + return 1 + +[case testLockAcquireBlocking] +from librt.threading import Lock + +def lock_acquire_blocking(lk: Lock, b: bool) -> bool: + return lk.acquire(b) +[out] +def lock_acquire_blocking(lk, b): + lk :: librt.threading.Lock + b, r0 :: bool +L0: + r0 = LibRTThreading_Lock_acquire_blocking_internal(lk, b) + return r0 + +[case testLockWith] +from librt.threading import Lock + +def with_lock(lk: Lock) -> None: + with lk: + a: list[int] = [] +[out] +def with_lock(lk): + lk :: librt.threading.Lock + r0 :: bool + r1, a :: list + r2, r3, r4 :: tuple[object, object, object] + r5 :: None + r6 :: bit +L0: + r0 = LibRTThreading_Lock_acquire_internal(lk) +L1: + r1 = PyList_New(0) + a = r1 +L2: +L3: + r2 = :: tuple[object, object, object] + r3 = r2 + goto L5 +L4: (handler for L1) + r4 = CPy_CatchError() + r3 = r4 +L5: + r5 = LibRTThreading_Lock_release_internal(lk) + if is_error(r3) goto L7 else goto L6 +L6: + CPy_Reraise() + unreachable +L7: + goto L11 +L8: (handler for L5, L6) + if is_error(r3) goto L10 else goto L9 +L9: + CPy_RestoreExcInfo(r3) +L10: + r6 = CPy_KeepPropagating() + unreachable +L11: + return 1 diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 9f6b0f5d07390..808b5b9e0d4ab 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -281,7 +281,7 @@ L5: L6: return r6 -[case testTupleBuiltFromList] +[case testTupleBuiltFromList_withgil] def f(val: int) -> bool: return val % 2 == 0 @@ -1018,7 +1018,7 @@ L5: a = r42 return 1 -[case testTupleBuiltFromStars] +[case testTupleBuiltFromStars_withgil] from typing import Final abc: Final = "abc" diff --git a/mypyc/test-data/irbuild-vec-i64.test b/mypyc/test-data/irbuild-vec-i64.test index aeab3ed9f2a8b..176e2616a4856 100644 --- a/mypyc/test-data/irbuild-vec-i64.test +++ b/mypyc/test-data/irbuild-vec-i64.test @@ -64,11 +64,7 @@ def f(v, i): r2 :: i64 r3 :: bit r4 :: bool - r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: i64 + r5, r6 :: i64 L0: r0 = v.len r1 = i < r0 :: unsigned @@ -86,15 +82,10 @@ L3: L4: r5 = i L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: i64* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[i64] v, r5 + return r6 [case testVecI64GetItem_32bit] -# The IR is quite verbose, but it's acceptable since 32-bit targets are not common any more from librt.vecs import vec from mypy_extensions import i64 @@ -110,13 +101,7 @@ def f(v, i): r3 :: i64 r4 :: bit r5 :: bool - r6 :: i64 - r7, r8 :: bit - r9 :: native_int - r10 :: ptr - r11 :: native_int - r12 :: ptr - r13 :: i64 + r6, r7 :: i64 L0: r0 = v.len r1 = extend signed r0: native_int to i64 @@ -135,24 +120,8 @@ L3: L4: r6 = i L5: - r7 = r6 < 2147483648 :: signed - if r7 goto L6 else goto L8 :: bool -L6: - r8 = r6 >= -2147483648 :: signed - if r8 goto L7 else goto L8 :: bool -L7: - r9 = truncate r6: i64 to native_int - goto L9 -L8: - CPyInt32_Overflow() - unreachable -L9: - r10 = v.items - r11 = r9 * 8 - r12 = r10 + r11 - r13 = load_mem r12 :: i64* - keep_alive v - return r13 + r7 = vec_get_item_unsafe[i64] v, r6 + return r7 [case testVecI64Append] from librt.vecs import vec, append @@ -365,7 +334,7 @@ L3: L4: return r1 -[case testVecI64FastComprehensionFromList] +[case testVecI64FastComprehensionFromList_nogil] from librt.vecs import vec from mypy_extensions import i64 from typing import List @@ -395,7 +364,7 @@ L1: r4 = r2 < r3 :: signed if r4 goto L2 else goto L4 :: bool L2: - r5 = list_get_item_unsafe l, r2 + r5 = CPyList_GetItemInt64(l, r2) r6 = unbox(i64, r5) x = r6 r7 = x + 1 @@ -411,7 +380,7 @@ L3: L4: return r1 -[case testVecI64FastComprehensionFromVec] +[case testVecI64FastComprehensionFromVec_64bit] from librt.vecs import vec from mypy_extensions import i64 from typing import List @@ -426,14 +395,11 @@ def f(n, v): r1 :: vec[i64] r2, r3 :: native_int r4 :: bit - r5 :: ptr - r6 :: native_int + r5, x, r6 :: i64 r7 :: ptr - r8, x, r9 :: i64 - r10 :: ptr - r11 :: native_int - r12 :: ptr - r13 :: native_int + r8 :: native_int + r9 :: ptr + r10 :: native_int L0: r0 = v.len r1 = VecI64Api.alloc(r0, r0) @@ -443,21 +409,17 @@ L1: r4 = r2 < r3 :: signed if r4 goto L2 else goto L4 :: bool L2: - r5 = v.items - r6 = r2 * 8 - r7 = r5 + r6 - r8 = load_mem r7 :: i64* - x = r8 - keep_alive v - r9 = x + 1 - r10 = r1.items - r11 = r2 * 8 - r12 = r10 + r11 - set_mem r12, r9 :: i64* + r5 = vec_get_item_unsafe[i64] v, r2 + x = r5 + r6 = x + 1 + r7 = r1.items + r8 = r2 * 8 + r9 = r7 + r8 + set_mem r9, r6 :: i64* keep_alive r1 L3: - r13 = r2 + 1 - r2 = r13 + r10 = r2 + 1 + r2 = r10 goto L1 L4: return r1 @@ -499,7 +461,7 @@ L3: L4: return r1 -[case testVecI64ForLoop] +[case testVecI64ForLoop_64bit] from librt.vecs import vec from mypy_extensions import i64 @@ -514,11 +476,8 @@ def f(v): t :: i64 r0, r1 :: native_int r2 :: bit - r3 :: ptr - r4 :: native_int - r5 :: ptr - r6, x, r7 :: i64 - r8 :: native_int + r3, x, r4 :: i64 + r5 :: native_int L0: t = 0 r0 = 0 @@ -527,17 +486,13 @@ L1: r2 = r0 < r1 :: signed if r2 goto L2 else goto L4 :: bool L2: - r3 = v.items - r4 = r0 * 8 - r5 = r3 + r4 - r6 = load_mem r5 :: i64* - x = r6 - keep_alive v - r7 = t + 1 - t = r7 + r3 = vec_get_item_unsafe[i64] v, r0 + x = r3 + r4 = t + 1 + t = r4 L3: - r8 = r0 + 1 - r0 = r8 + r5 = r0 + 1 + r0 = r5 goto L1 L4: return t @@ -601,11 +556,7 @@ def f(v): r2 :: i64 r3 :: bit r4 :: bool - r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: i64 + r5, r6 :: i64 L0: r0 = v.len r1 = 0 < r0 :: unsigned @@ -623,12 +574,8 @@ L3: L4: r5 = 0 L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: i64* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[i64] v, r5 + return r6 [case testVecI64Slicing_64bit] from librt.vecs import vec @@ -720,20 +667,16 @@ def inplace(v, n, m): r2 :: i64 r3 :: bit r4 :: bool - r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9, r10 :: i64 - r11 :: native_int - r12 :: bit + r5, r6, r7 :: i64 + r8 :: native_int + r9 :: bit + r10 :: i64 + r11 :: bit + r12 :: bool r13 :: i64 - r14 :: bit - r15 :: bool - r16 :: i64 - r17 :: ptr - r18 :: i64 - r19 :: ptr + r14 :: ptr + r15 :: i64 + r16 :: ptr L0: r0 = v.len r1 = n < r0 :: unsigned @@ -751,32 +694,28 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: i64* - keep_alive v - r10 = r9 + m - r11 = v.len - r12 = n < r11 :: unsigned - if r12 goto L9 else goto L6 :: bool + r6 = vec_get_item_unsafe[i64] v, r5 + r7 = r6 + m + r8 = v.len + r9 = n < r8 :: unsigned + if r9 goto L9 else goto L6 :: bool L6: - r13 = n + r11 - r14 = r13 < r11 :: unsigned - if r14 goto L8 else goto L7 :: bool + r10 = n + r8 + r11 = r10 < r8 :: unsigned + if r11 goto L8 else goto L7 :: bool L7: - r15 = raise IndexError + r12 = raise IndexError unreachable L8: - r16 = r13 + r13 = r10 goto L10 L9: - r16 = n + r13 = n L10: - r17 = v.items - r18 = r16 * 8 - r19 = r17 + r18 - set_mem r19, r10 :: i64* + r14 = v.items + r15 = r13 * 8 + r16 = r14 + r15 + set_mem r16, r7 :: i64* keep_alive v return 1 diff --git a/mypyc/test-data/irbuild-vec-misc.test b/mypyc/test-data/irbuild-vec-misc.test index c0d4325e38fcc..22037d8597a73 100644 --- a/mypyc/test-data/irbuild-vec-misc.test +++ b/mypyc/test-data/irbuild-vec-misc.test @@ -125,10 +125,7 @@ def get_item_bool(v, i): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: bool + r6 :: bool L0: r0 = v.len r1 = i < r0 :: unsigned @@ -146,12 +143,8 @@ L3: L4: r5 = i L5: - r6 = v.items - r7 = r5 * 1 - r8 = r6 + r7 - r9 = load_mem r8 :: builtins.bool* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[bool] v, r5 + return r6 [case testVecMiscPop] from librt.vecs import vec, pop @@ -209,7 +202,7 @@ L0: r0 = VecFloatApi.slice(v, x, y) return r0 -[case testVecMiscForLoop] +[case testVecMiscForLoop_64bit] from librt.vecs import vec, remove from mypy_extensions import i64, i16 @@ -225,11 +218,8 @@ def for_bool(v): s :: i16 r0, r1 :: native_int r2 :: bit - r3 :: ptr - r4 :: native_int - r5 :: ptr - r6, x, r7 :: i16 - r8 :: native_int + r3, x, r4 :: i16 + r5 :: native_int L0: s = 0 r0 = 0 @@ -238,17 +228,13 @@ L1: r2 = r0 < r1 :: signed if r2 goto L2 else goto L4 :: bool L2: - r3 = v.items - r4 = r0 * 2 - r5 = r3 + r4 - r6 = load_mem r5 :: i16* - x = r6 - keep_alive v - r7 = s + x - s = r7 + r3 = vec_get_item_unsafe[i16] v, r0 + x = r3 + r4 = s + x + s = r4 L3: - r8 = r0 + 1 - r0 = r8 + r5 = r0 + 1 + r0 = r5 goto L1 L4: return s @@ -270,10 +256,7 @@ def get_item_nested(v, i): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[i32] + r6 :: vec[i32] L0: r0 = v.len r1 = i < r0 :: unsigned @@ -291,12 +274,8 @@ L3: L4: r5 = i L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = load_mem r8 :: vec[i32]* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[vec[i32]] v, r5 + return r6 [case testVecMiscNestedPop_64bit] from librt.vecs import vec, pop diff --git a/mypyc/test-data/irbuild-vec-nested.test b/mypyc/test-data/irbuild-vec-nested.test index 1fe42a880d5b0..dd49d9475812f 100644 --- a/mypyc/test-data/irbuild-vec-nested.test +++ b/mypyc/test-data/irbuild-vec-nested.test @@ -206,10 +206,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[str] + r6 :: vec[str] L0: r0 = v.len r1 = n < r0 :: unsigned @@ -227,12 +224,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = load_mem r8 :: vec[str]* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[vec[str]] v, r5 + return r6 [case testVecNestedI64GetItem_64bit] from librt.vecs import vec @@ -250,10 +243,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[i64] + r6 :: vec[i64] L0: r0 = v.len r1 = n < r0 :: unsigned @@ -271,12 +261,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = load_mem r8 :: vec[i64]* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[vec[i64]] v, r5 + return r6 [case testVecNestedI64GetItemWithBorrow_64bit] from librt.vecs import vec @@ -294,20 +280,13 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[i64] - r10 :: native_int - r11 :: bit - r12 :: i64 - r13 :: bit - r14 :: bool - r15 :: i64 - r16 :: ptr - r17 :: i64 - r18 :: ptr - r19 :: i64 + r6 :: vec[i64] + r7 :: native_int + r8 :: bit + r9 :: i64 + r10 :: bit + r11 :: bool + r12, r13 :: i64 L0: r0 = v.len r1 = n < r0 :: unsigned @@ -325,32 +304,94 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = borrow load_mem r8 :: vec[i64]* - r10 = r9.len - r11 = n < r10 :: unsigned - if r11 goto L9 else goto L6 :: bool + r6 = vec_get_item_unsafe_borrow[vec[i64]] v, r5 + r7 = r6.len + r8 = n < r7 :: unsigned + if r8 goto L9 else goto L6 :: bool +L6: + r9 = n + r7 + r10 = r9 < r7 :: unsigned + if r10 goto L8 else goto L7 :: bool +L7: + r11 = raise IndexError + unreachable +L8: + r12 = r9 + goto L10 +L9: + r12 = n +L10: + r13 = vec_get_item_unsafe[i64] r6, r12 + keep_alive v, r5 + return r13 + +[case testVecNestedStrGetItemWithBorrow_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +class C: + v: vec[vec[str]] + + def f(self, n: i64) -> str: + # The intermediate vec is borrowed, but the result must be owned. + return self.v[n][n] +[out] +def C.f(self, n): + self :: __main__.C + n :: i64 + r0 :: vec[vec[str]] + r1 :: native_int + r2 :: bit + r3 :: i64 + r4 :: bit + r5 :: bool + r6 :: i64 + r7 :: vec[str] + r8 :: native_int + r9 :: bit + r10 :: i64 + r11 :: bit + r12 :: bool + r13 :: i64 + r14 :: str +L0: + r0 = borrow self.v + r1 = r0.len + r2 = n < r1 :: unsigned + if r2 goto L4 else goto L1 :: bool +L1: + r3 = n + r1 + r4 = r3 < r1 :: unsigned + if r4 goto L3 else goto L2 :: bool +L2: + r5 = raise IndexError + unreachable +L3: + r6 = r3 + goto L5 +L4: + r6 = n +L5: + r7 = vec_get_item_unsafe_borrow[vec[str]] r0, r6 + r8 = r7.len + r9 = n < r8 :: unsigned + if r9 goto L9 else goto L6 :: bool L6: - r12 = n + r10 - r13 = r12 < r10 :: unsigned - if r13 goto L8 else goto L7 :: bool + r10 = n + r8 + r11 = r10 < r8 :: unsigned + if r11 goto L8 else goto L7 :: bool L7: - r14 = raise IndexError + r12 = raise IndexError unreachable L8: - r15 = r12 + r13 = r10 goto L10 L9: - r15 = n + r13 = n L10: - r16 = r9.items - r17 = r15 * 8 - r18 = r16 + r17 - r19 = load_mem r18 :: i64* - keep_alive v, r9 - return r19 + r14 = vec_get_item_unsafe[str] r7, r13 + keep_alive self, r0, r6 + return r14 [case testVecDoublyNestedGetItem_64bit] from librt.vecs import vec @@ -368,10 +409,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[vec[str]] + r6 :: vec[vec[str]] L0: r0 = v.len r1 = n < r0 :: unsigned @@ -389,12 +427,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = load_mem r8 :: vec[vec[str]]* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[vec[vec[str]]] v, r5 + return r6 [case testVecNestedCreateWithCap_64bit] from librt.vecs import vec diff --git a/mypyc/test-data/irbuild-vec-t.test b/mypyc/test-data/irbuild-vec-t.test index 63ad14bc2d7a2..ce162bd0806c7 100644 --- a/mypyc/test-data/irbuild-vec-t.test +++ b/mypyc/test-data/irbuild-vec-t.test @@ -215,10 +215,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: str + r6 :: str L0: r0 = v.len r1 = n < r0 :: unsigned @@ -236,12 +233,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: builtins.str* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[str] v, r5 + return r6 [case testVecTOptionalGetItem_64bit] from librt.vecs import vec @@ -260,10 +253,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: union[str, None] + r6 :: union[str, None] L0: r0 = v.len r1 = n < r0 :: unsigned @@ -281,12 +271,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: union* - keep_alive v - return r9 + r6 = vec_get_item_unsafe[union[str, None]] v, r5 + return r6 [case testNewTPopLast] from typing import Tuple @@ -524,3 +510,62 @@ L0: r1 = r0 r2 = VecTApi.from_iterable(r1, a, 0) return r2 + +[case testVecTBorrowGetItem_64bit] +from typing import Final + +from librt.vecs import vec +from mypy_extensions import i64 + +class A: + def __init__(self, x: str) -> None: + # Final to allow borrowing on free-threaded builds + self.x: Final = x + +def f(v: vec[A], n: i64) -> int: + return len(v[n].x) +[out] +def A.__init__(self, x): + self :: __main__.A + x :: str +L0: + self.x = x + return 1 +def f(v, n): + v :: vec[__main__.A] + n :: i64 + r0 :: native_int + r1 :: bit + r2 :: i64 + r3 :: bit + r4 :: bool + r5 :: i64 + r6 :: __main__.A + r7 :: str + r8 :: native_int + r9 :: bit + r10 :: short_int +L0: + r0 = v.len + r1 = n < r0 :: unsigned + if r1 goto L4 else goto L1 :: bool +L1: + r2 = n + r0 + r3 = r2 < r0 :: unsigned + if r3 goto L3 else goto L2 :: bool +L2: + r4 = raise IndexError + unreachable +L3: + r5 = r2 + goto L5 +L4: + r5 = n +L5: + r6 = vec_get_item_unsafe_borrow[__main__.A] v, r5 + r7 = r6.x + keep_alive v, r5 + r8 = CPyStr_Size_size_t(r7) + r9 = r8 >= 0 :: signed + r10 = r8 << 1 + return r10 diff --git a/mypyc/test-data/lowering-int.test b/mypyc/test-data/lowering-int.test index c2bcba54e444d..72c4a82dcaea3 100644 --- a/mypyc/test-data/lowering-int.test +++ b/mypyc/test-data/lowering-int.test @@ -332,7 +332,7 @@ L4: L5: return 4 -[case testLowerIntForLoop_64bit] +[case testLowerIntForLoop_withgil_64bit] from __future__ import annotations def f(l: list[int]) -> None: diff --git a/mypyc/test-data/lowering-vec.test b/mypyc/test-data/lowering-vec.test new file mode 100644 index 0000000000000..374d336b98d8e --- /dev/null +++ b/mypyc/test-data/lowering-vec.test @@ -0,0 +1,155 @@ +[case testLowerVecI64GetItem_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(i: i64) -> i64: + v = vec[i64]() + return v[i] +[out] +def f(i): + i :: i64 + r0, v :: vec[i64] + r1 :: native_int + r2 :: bit + r3 :: i64 + r4 :: bit + r5 :: bool + r6 :: i64 + r7 :: ptr + r8 :: i64 + r9 :: ptr + r10, r11 :: i64 +L0: + r0 = VecI64Api.alloc(0, 0) + if is_error(r0) goto L8 (error at f:5) else goto L1 +L1: + v = r0 + r1 = v.len + r2 = i < r1 :: unsigned + if r2 goto L6 else goto L2 :: bool +L2: + r3 = i + r1 + r4 = r3 < r1 :: unsigned + if r4 goto L5 else goto L9 :: bool +L3: + r5 = raise IndexError + if not r5 goto L8 (error at f:6) else goto L4 :: bool +L4: + unreachable +L5: + r6 = r3 + goto L7 +L6: + r6 = i +L7: + r7 = v.items + r8 = r6 * 8 + r9 = r7 + r8 + r10 = load_mem r9 :: i64* + dec_ref v + return r10 +L8: + r11 = :: i64 + return r11 +L9: + dec_ref v + goto L3 + +[case testLowerVecNestedGetItem_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(i: i64, j: i64) -> str: + v = vec[vec[str]]([]) + return v[i][j] +[out] +def f(i, j): + i, j :: i64 + r0 :: object + r1 :: ptr + r2 :: vec[vec[str]] + r3 :: ptr + v :: vec[vec[str]] + r4 :: native_int + r5 :: bit + r6 :: i64 + r7 :: bit + r8 :: bool + r9 :: i64 + r10 :: ptr + r11 :: i64 + r12 :: ptr + r13 :: vec[str] + r14 :: native_int + r15 :: bit + r16 :: i64 + r17 :: bit + r18 :: bool + r19 :: i64 + r20 :: ptr + r21 :: i64 + r22 :: ptr + r23, r24 :: str +L0: + r0 = load_address PyUnicode_Type + r1 = r0 + r2 = VecNestedApi.alloc(0, 0, r1, 1) + if is_error(r2) goto L14 (error at f:5) else goto L1 +L1: + r3 = r2.items + v = r2 + r4 = v.len + r5 = i < r4 :: unsigned + if r5 goto L6 else goto L2 :: bool +L2: + r6 = i + r4 + r7 = r6 < r4 :: unsigned + if r7 goto L5 else goto L15 :: bool +L3: + r8 = raise IndexError + if not r8 goto L14 (error at f:6) else goto L4 :: bool +L4: + unreachable +L5: + r9 = r6 + goto L7 +L6: + r9 = i +L7: + r10 = v.items + r11 = r9 * 16 + r12 = r10 + r11 + r13 = borrow load_mem r12 :: vec[str]* + r14 = r13.len + r15 = j < r14 :: unsigned + if r15 goto L12 else goto L8 :: bool +L8: + r16 = j + r14 + r17 = r16 < r14 :: unsigned + if r17 goto L11 else goto L16 :: bool +L9: + r18 = raise IndexError + if not r18 goto L14 (error at f:6) else goto L10 :: bool +L10: + unreachable +L11: + r19 = r16 + goto L13 +L12: + r19 = j +L13: + r20 = r13.items + r21 = r19 * 8 + r22 = r20 + r21 + r23 = load_mem r22 :: builtins.str* + dec_ref v + return r23 +L14: + r24 = :: str + return r24 +L15: + dec_ref v + goto L3 +L16: + dec_ref v + goto L9 diff --git a/mypyc/test-data/opt-copy-propagation.test b/mypyc/test-data/opt-copy-propagation.test index 49b80f4385fc4..9fa5edb579fac 100644 --- a/mypyc/test-data/opt-copy-propagation.test +++ b/mypyc/test-data/opt-copy-propagation.test @@ -185,7 +185,7 @@ L1: L2: return 2 -[case testIRTransformRegisterOps1] +[case testIRTransformRegisterOps1_withgil] from __future__ import annotations from typing import cast diff --git a/mypyc/test-data/refcount.test b/mypyc/test-data/refcount.test index 2f03a159dfacb..bedfabac791d4 100644 --- a/mypyc/test-data/refcount.test +++ b/mypyc/test-data/refcount.test @@ -592,6 +592,28 @@ L0: dec_ref d return 1 +[case testBorrowListObjectWithLiteralIndex_nogil] +from typing import cast + +def f() -> str: + o = cast(object, []) + return cast(list[str], o)[0] +[out] +def f(): + r0 :: list + o :: object + r1 :: list + r2 :: object + r3 :: str +L0: + r0 = PyList_New(0) + o = r0 + r1 = borrow cast(list, o) + r2 = CPyList_GetItemShort(r1, 0) + r3 = cast(str, r2) + dec_ref o + return r3 + [case testUnaryBranchSpecialCase] def f(x: bool) -> int: if x: @@ -629,7 +651,7 @@ L0: r4 = (r2, r3) return r4 -[case testDecomposeTuple] +[case testDecomposeTuple_withgil] from typing import Tuple class C: @@ -948,7 +970,7 @@ L0: dec_ref r4 return r5 -[case testBorrowAttribute] +[case testBorrowAttribute_withgil] def g() -> int: d = D() return d.c.x @@ -981,7 +1003,99 @@ L0: r1 = r0.x return r1 -[case testBorrowAttributeTwice] +[case testBorrowAttribute_nogil] +from typing import Final + +def g() -> int: + d = D(C(1)) + return d.c.x + +def f(d: D) -> int: + return d.c.x + +class C: + def __init__(self, x: int) -> None: + self.x: Final = x +class D: + def __init__(self, c: C) -> None: + self.c: Final = c +[out] +def g(): + r0 :: __main__.C + r1, d :: __main__.D + r2 :: __main__.C + r3 :: int +L0: + r0 = C(2) + r1 = D(r0) + dec_ref r0 + d = r1 + r2 = borrow d.c + r3 = r2.x + dec_ref d + return r3 +def f(d): + d :: __main__.D + r0 :: __main__.C + r1 :: int +L0: + r0 = borrow d.c + r1 = r0.x + return r1 +def C.__init__(self, x): + self :: __main__.C + x :: int +L0: + inc_ref x :: int + self.x = x + return 1 +def D.__init__(self, c): + self :: __main__.D + c :: __main__.C +L0: + inc_ref c + self.c = c + return 1 + +[case testBorrowInheritedFinalAttribute_nogil] +from typing import Final + +def f(d: D) -> int: + return d.c.x + +class B: + def __init__(self, x: int) -> None: + self.x: Final = x +class C(B): + pass +class D: + def __init__(self, c: C) -> None: + self.c: Final = c +[out] +def f(d): + d :: __main__.D + r0 :: __main__.C + r1 :: int +L0: + r0 = borrow d.c + r1 = r0.x + return r1 +def B.__init__(self, x): + self :: __main__.B + x :: int +L0: + inc_ref x :: int + self.x = x + return 1 +def D.__init__(self, c): + self :: __main__.D + c :: __main__.C +L0: + inc_ref c + self.c = c + return 1 + +[case testBorrowAttributeTwice_withgil] def f(e: E) -> int: return e.d.c.x @@ -1003,7 +1117,7 @@ L0: r2 = r1.x return r2 -[case testBorrowAttributeIsNone] +[case testBorrowAttributeIsNone_withgil] from typing import Optional def f(c: C) -> bool: @@ -1036,7 +1150,7 @@ L0: r2 = r0 == r1 return r2 -[case testBorrowAttributeNarrowOptional] +[case testBorrowAttributeNarrowOptional_withgil] from typing import Optional def f(c: C) -> bool: @@ -1071,7 +1185,7 @@ L1: L2: return 0 -[case testBorrowLenArgument] +[case testBorrowLenArgument_withgil] from typing import List def f(x: C) -> int: @@ -1091,7 +1205,7 @@ L0: r2 = r1 << 1 return r2 -[case testBorrowIsinstanceArgument] +[case testBorrowIsinstanceArgument_withgil] from typing import List def f(x: C) -> bool: @@ -1130,7 +1244,7 @@ L1: L2: return 1 -[case testBorrowListGetItem1] +[case testBorrowListGetItem1_withgil] from typing import List def literal_index(x: C) -> str: @@ -1178,7 +1292,58 @@ L0: r2 = cast(str, r1) return r2 -[case testBorrowListGetItem2] +[case testBorrowListGetItem1_nogil] +from typing import List + +def literal_index(x: C) -> str: + return x.a[0] + +def negative_index(x: C) -> str: + return x.a[-1] + +def lvar_index(x: C, n: int) -> str: + return x.a[n] + +class C: + a: List[str] + +[out] +def literal_index(x): + x :: __main__.C + r0 :: list + r1 :: object + r2 :: str +L0: + r0 = x.a + r1 = CPyList_GetItemShort(r0, 0) + dec_ref r0 + r2 = cast(str, r1) + return r2 +def negative_index(x): + x :: __main__.C + r0 :: list + r1 :: object + r2 :: str +L0: + r0 = x.a + r1 = CPyList_GetItemShort(r0, -2) + dec_ref r0 + r2 = cast(str, r1) + return r2 +def lvar_index(x, n): + x :: __main__.C + n :: int + r0 :: list + r1 :: object + r2 :: str +L0: + r0 = x.a + r1 = CPyList_GetItem(r0, n) + dec_ref r0 + r2 = cast(str, r1) + return r2 + +[case testBorrowListGetItem2_withgil] from typing import List def attr_before_index(x: C) -> str: @@ -1228,6 +1393,60 @@ L0: r2 = r1.n return r2 +[case testBorrowListGetItem2_nogil] +from typing import List + +def attr_before_index(x: C) -> str: + return x.a[x.n] + +def attr_after_index(a: List[C], i: int) -> int: + return a[i].n + +def attr_after_index_literal(a: List[C]) -> int: + return a[0].n + +class C: + a: List[str] + n: int +[out] +def attr_before_index(x): + x :: __main__.C + r0 :: list + r1 :: int + r2 :: object + r3 :: str +L0: + r0 = x.a + r1 = x.n + r2 = CPyList_GetItem(r0, r1) + dec_ref r0 + dec_ref r1 :: int + r3 = cast(str, r2) + return r3 +def attr_after_index(a, i): + a :: list + i :: int + r0 :: object + r1 :: __main__.C + r2 :: int +L0: + r0 = CPyList_GetItem(a, i) + r1 = cast(__main__.C, r0) + r2 = r1.n + dec_ref r1 + return r2 +def attr_after_index_literal(a): + a :: list + r0 :: object + r1 :: __main__.C + r2 :: int +L0: + r0 = CPyList_GetItemShort(a, 0) + r1 = cast(__main__.C, r0) + r2 = r1.n + dec_ref r1 + return r2 + [case testCannotBorrowListGetItem] from typing import List @@ -1257,7 +1476,7 @@ def f(): L0: return 0 -[case testBorrowListGetItemKeepAlive] +[case testBorrowListGetItemKeepAlive_withgil] from typing import List def f() -> str: @@ -1287,7 +1506,7 @@ L0: dec_ref a return r5 -[case testBorrowSetAttrObject] +[case testBorrowSetAttrObject_withgil] from typing import Optional def f(x: Optional[C]) -> None: @@ -1327,7 +1546,7 @@ L0: r0.b = 0; r1 = is_error return 1 -[case testBorrowIntEquality] +[case testBorrowIntEquality_withgil] def add(c: C) -> bool: return c.x == c.y @@ -1345,7 +1564,7 @@ L0: r2 = int_eq r0, r1 return r2 -[case testBorrowIntLessThan] +[case testBorrowIntLessThan_withgil] def add(c: C) -> bool: return c.x < c.y @@ -1363,7 +1582,7 @@ L0: r2 = int_lt r0, r1 return r2 -[case testBorrowIntCompareFinal] +[case testBorrowIntCompareFinal_withgil] from typing import Final X: Final = 10 @@ -1383,7 +1602,7 @@ L0: r1 = int_eq r0, 20 return r1 -[case testBorrowIntArithmetic] +[case testBorrowIntArithmetic_withgil] def add(c: C) -> int: return c.x + c.y @@ -1411,7 +1630,39 @@ L0: r2 = CPyTagged_Subtract(r0, r1) return r2 -[case testBorrowIntComparisonInIf] +[case testBorrowIntArithmetic_nogil] +def add(c: C) -> int: + return c.x + c.y + +def sub(c: C) -> int: + return c.x - c.y + +class C: + x: int + y: int +[out] +def add(c): + c :: __main__.C + r0, r1, r2 :: int +L0: + r0 = c.x + r1 = c.y + r2 = CPyTagged_Add(r0, r1) + dec_ref r0 :: int + dec_ref r1 :: int + return r2 +def sub(c): + c :: __main__.C + r0, r1, r2 :: int +L0: + r0 = c.x + r1 = c.y + r2 = CPyTagged_Subtract(r0, r1) + dec_ref r0 :: int + dec_ref r1 :: int + return r2 + +[case testBorrowIntComparisonInIf_withgil] def add(c: C, n: int) -> bool: if c.x == c.y: return True @@ -1435,7 +1686,7 @@ L1: L2: return 0 -[case testBorrowIntInPlaceOp] +[case testBorrowIntInPlaceOp_withgil] def add(c: C, n: int) -> None: c.x += n @@ -1606,12 +1857,9 @@ def f(v): t :: i64 r0, r1 :: native_int r2 :: bit - r3 :: ptr - r4 :: native_int - r5 :: ptr - r6, s :: str - r7 :: None - r8 :: native_int + r3, s :: str + r4 :: None + r5 :: native_int L0: t = 0 r0 = 0 @@ -1620,16 +1868,13 @@ L1: r2 = r0 < r1 :: signed if r2 goto L2 else goto L4 :: bool L2: - r3 = v.items - r4 = r0 * 8 - r5 = r3 + r4 - r6 = load_mem r5 :: builtins.str* - s = r6 - r7 = g(s) + r3 = vec_get_item_unsafe[str] v, r0 + s = r3 + r4 = g(s) dec_ref s L3: - r8 = r0 + 1 - r0 = r8 + r5 = r0 + 1 + r0 = r5 goto L1 L4: return t @@ -1684,11 +1929,7 @@ def C.f(self, x): r3 :: i64 r4 :: bit r5 :: bool - r6 :: i64 - r7 :: ptr - r8 :: i64 - r9 :: ptr - r10 :: i64 + r6, r7 :: i64 L0: r0 = borrow self.v r1 = r0.len @@ -1707,11 +1948,8 @@ L3: L4: r6 = x L5: - r7 = r0.items - r8 = r6 * 8 - r9 = r7 + r8 - r10 = load_mem r9 :: i64* - return r10 + r7 = vec_get_item_unsafe[i64] r0, r6 + return r7 [case testVecI64LenBorrowVec_64bit] from librt.vecs import vec @@ -1771,10 +2009,7 @@ def f(v, n): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: str + r6 :: str L0: r0 = v.len r1 = n < r0 :: unsigned @@ -1792,11 +2027,8 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 8 - r8 = r6 + r7 - r9 = load_mem r8 :: builtins.str* - return r9 + r6 = vec_get_item_unsafe[str] v, r5 + return r6 [case testVecNestedGetItem_64bit] from librt.vecs import vec @@ -1817,10 +2049,7 @@ def f(v, n): r6 :: bit r7 :: bool r8 :: i64 - r9 :: ptr - r10 :: i64 - r11 :: ptr - r12, vv :: vec[str] + r9, vv :: vec[str] L0: r0 = load_address PyUnicode_Type r1 = r0 @@ -1841,12 +2070,9 @@ L3: L4: r8 = n L5: - r9 = r2.items - r10 = r8 * 16 - r11 = r9 + r10 - r12 = load_mem r11 :: vec[str]* + r9 = vec_get_item_unsafe[vec[str]] r2, r8 dec_ref r2 - vv = r12 + vv = r9 dec_ref vv return 1 L6: @@ -1869,20 +2095,13 @@ def f(v, n, m): r3 :: bit r4 :: bool r5 :: i64 - r6 :: ptr - r7 :: i64 - r8 :: ptr - r9 :: vec[i64] - r10 :: native_int - r11 :: bit - r12 :: i64 - r13 :: bit - r14 :: bool - r15 :: i64 - r16 :: ptr - r17 :: i64 - r18 :: ptr - r19 :: i64 + r6 :: vec[i64] + r7 :: native_int + r8 :: bit + r9 :: i64 + r10 :: bit + r11 :: bool + r12, r13 :: i64 L0: r0 = v.len r1 = n < r0 :: unsigned @@ -1900,31 +2119,25 @@ L3: L4: r5 = n L5: - r6 = v.items - r7 = r5 * 16 - r8 = r6 + r7 - r9 = borrow load_mem r8 :: vec[i64]* - r10 = r9.len - r11 = m < r10 :: unsigned - if r11 goto L9 else goto L6 :: bool + r6 = vec_get_item_unsafe_borrow[vec[i64]] v, r5 + r7 = r6.len + r8 = m < r7 :: unsigned + if r8 goto L9 else goto L6 :: bool L6: - r12 = m + r10 - r13 = r12 < r10 :: unsigned - if r13 goto L8 else goto L7 :: bool + r9 = m + r7 + r10 = r9 < r7 :: unsigned + if r10 goto L8 else goto L7 :: bool L7: - r14 = raise IndexError + r11 = raise IndexError unreachable L8: - r15 = r12 + r12 = r9 goto L10 L9: - r15 = m + r12 = m L10: - r16 = r9.items - r17 = r15 * 8 - r18 = r16 + r17 - r19 = load_mem r18 :: i64* - return r19 + r13 = vec_get_item_unsafe[i64] r6, r12 + return r13 [case testVecPop] from librt.vecs import vec, pop, append @@ -2125,3 +2338,745 @@ L3: r8 = box(None, 1) inc_ref r8 return r8 + +[case testBytesConcatRefcount] +def f(a: bytes, b: bytes) -> bytes: + return b"1" + a + b +[out] +def f(a, b): + a, b, r0, r1, r2 :: bytes +L0: + r0 = b'1' + r1 = CPyBytes_Concat(r0, a) + r2 = CPyBytes_Concat(r1, b) + dec_ref r1 + return r2 + +[case testChainedBytesConcatRefcount] +def f(a: bytes, b: bytes, c: bytes) -> bytes: + return b"1" + a + b + c +[out] +def f(a, b, c): + a, b, c, r0, r1, r2, r3 :: bytes +L0: + r0 = b'1' + r1 = CPyBytes_Concat(r0, a) + r2 = CPyBytes_Concat(r1, b) + dec_ref r1 + r3 = CPyBytes_Concat(r2, c) + dec_ref r2 + return r3 + +[case testBorrowedFinalAttribute] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(c: C, b: bool) -> str: + a = c.s if b else "x" + return a + +def g(c: C) -> C: + return C(c.s) +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(c, b): + c :: __main__.C + b :: bool + r0, r1, r2, a :: str +L0: + if b goto L1 else goto L2 :: bool +L1: + r0 = borrow c.s + inc_ref r0 + r1 = r0 + goto L3 +L2: + r2 = 'x' + inc_ref r2 + r1 = r2 +L3: + a = r1 + return a +def g(c): + c :: __main__.C + r0 :: str + r1 :: __main__.C +L0: + r0 = borrow c.s + r1 = C(r0) + return r1 + +[case testBorrowedFinalAttribute2_withgil] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def g(c: C, n: int) -> str: + a = [c] + return a[n].s + "a" +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def g(c, n): + c :: __main__.C + n :: int + r0 :: list + r1 :: ptr + a :: list + r2 :: object + r3 :: __main__.C + r4, r5, r6 :: str +L0: + r0 = PyList_New(1) + r1 = list_items r0 + inc_ref c + buf_init_item r1, 0, c + a = r0 + r2 = CPyList_GetItemBorrow(a, n) + r3 = borrow cast(__main__.C, r2) + r4 = r3.s + dec_ref a + r5 = 'a' + r6 = PyUnicode_Concat(r4, r5) + dec_ref r4 + return r6 + +[case testBorrowedFinalAttribute3_withgil] +from typing import Final + +class C: + def __init__(self, d: D) -> None: + self.d = d + +class CC: + def __init__(self, d: D) -> None: + self.d: Final = d + +class D: + def __init__(self, s: str) -> None: + self.f: Final = s + +def f() -> str: + c = C(D("x")) + return c.d.f + "a" + +def g(d: D) -> str: + c = CC(d) + return c.d.f + "a" +[out] +def C.__init__(self, d): + self :: __main__.C + d :: __main__.D +L0: + inc_ref d + self.d = d + return 1 +def CC.__init__(self, d): + self :: __main__.CC + d :: __main__.D +L0: + inc_ref d + self.d = d + return 1 +def D.__init__(self, s): + self :: __main__.D + s :: str +L0: + inc_ref s + self.f = s + return 1 +def f(): + r0 :: str + r1 :: __main__.D + r2, c :: __main__.C + r3 :: __main__.D + r4, r5, r6 :: str +L0: + r0 = 'x' + r1 = D(r0) + r2 = C(r1) + dec_ref r1 + c = r2 + r3 = borrow c.d + r4 = r3.f + dec_ref c + r5 = 'a' + r6 = PyUnicode_Concat(r4, r5) + dec_ref r4 + return r6 +def g(d): + d :: __main__.D + r0, c :: __main__.CC + r1 :: __main__.D + r2, r3, r4 :: str +L0: + r0 = CC(d) + c = r0 + r1 = borrow c.d + r2 = borrow r1.f + r3 = 'a' + r4 = PyUnicode_Concat(r2, r3) + dec_ref c + return r4 + +[case testBorrowedFinalAttribute4] +from typing import Final + +class C: + def __init__(self, d: D) -> None: + self.d: Final = d + + def method(self) -> int: + return self.d.foo() + +class D: + def foo(self) -> int: + return 1 +[out] +def C.__init__(self, d): + self :: __main__.C + d :: __main__.D +L0: + inc_ref d + self.d = d + return 1 +def C.method(self): + self :: __main__.C + r0 :: __main__.D + r1 :: int +L0: + r0 = borrow self.d + r1 = r0.foo() + return r1 +def D.foo(self): + self :: __main__.D +L0: + return 2 + +[case testBorrowedFinalAttribute5_64bit] +from typing import Final +from librt.vecs import vec +from mypy_extensions import i64 + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def g(c: C, n: i64) -> str: + a = vec[C]([c]) + return a[n].s + "a" +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def g(c, n): + c :: __main__.C + n :: i64 + r0 :: object + r1 :: ptr + r2 :: vec[__main__.C] + r3, r4 :: ptr + a :: vec[__main__.C] + r5 :: native_int + r6 :: bit + r7 :: i64 + r8 :: bit + r9 :: bool + r10 :: i64 + r11 :: __main__.C + r12, r13, r14 :: str +L0: + r0 = __main__.C :: type + r1 = r0 + r2 = VecTApi.alloc(1, 1, r1) + r3 = r2.items + inc_ref c + set_mem r3, c :: __main__.C* + r4 = r3 + 8 + a = r2 + r5 = a.len + r6 = n < r5 :: unsigned + if r6 goto L4 else goto L1 :: bool +L1: + r7 = n + r5 + r8 = r7 < r5 :: unsigned + if r8 goto L3 else goto L6 :: bool +L2: + r9 = raise IndexError + unreachable +L3: + r10 = r7 + goto L5 +L4: + r10 = n +L5: + r11 = vec_get_item_unsafe_borrow[__main__.C] a, r10 + r12 = r11.s + dec_ref a + r13 = 'a' + r14 = PyUnicode_Concat(r12, r13) + dec_ref r12 + return r14 +L6: + dec_ref a + goto L2 + +[case testBorrowedFinalAttributeReassignViaWalrus] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(c: C, d: C) -> str: + return c.s + (c := d).s +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(c, d): + c, d :: __main__.C + r0, r1, r2 :: str +L0: + r0 = c.s + inc_ref d + c = d + dec_ref c + r1 = borrow d.s + r2 = PyUnicode_Concat(r0, r1) + dec_ref r0 + return r2 + +[case testBorrowedFinalAttributeConditionalMerge] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(b: bool) -> str: + return (C("x").s if b else "y") + "z" +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(b): + b :: bool + r0 :: str + r1 :: __main__.C + r2, r3, r4, r5, r6 :: str +L0: + if b goto L1 else goto L2 :: bool +L1: + r0 = 'x' + r1 = C(r0) + r2 = borrow r1.s + inc_ref r2 + r3 = r2 + dec_ref r1 + goto L3 +L2: + r4 = 'y' + inc_ref r4 + r3 = r4 +L3: + r5 = 'z' + r6 = PyUnicode_Concat(r3, r5) + dec_ref r3 + return r6 + +[case testBorrowedFinalAttributeAndMerge] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(s: str) -> str: + return (s and C("x").s) + "z" +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(s): + s :: str + r0 :: bit + r1, r2 :: str + r3 :: __main__.C + r4, r5, r6 :: str +L0: + r0 = CPyStr_IsTrue(s) + if r0 goto L2 else goto L1 :: bool +L1: + inc_ref s + r1 = s + goto L3 +L2: + r2 = 'x' + r3 = C(r2) + r4 = borrow r3.s + inc_ref r4 + r1 = r4 + dec_ref r3 +L3: + r5 = 'z' + r6 = PyUnicode_Concat(r1, r5) + dec_ref r1 + return r6 + +[case testBorrowedFinalAttributeOrMerge] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(s: str) -> str: + return (s or C("x").s) + "z" +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(s): + s :: str + r0 :: bit + r1, r2 :: str + r3 :: __main__.C + r4, r5, r6 :: str +L0: + r0 = CPyStr_IsTrue(s) + if r0 goto L1 else goto L2 :: bool +L1: + inc_ref s + r1 = s + goto L3 +L2: + r2 = 'x' + r3 = C(r2) + r4 = borrow r3.s + inc_ref r4 + r1 = r4 + dec_ref r3 +L3: + r5 = 'z' + r6 = PyUnicode_Concat(r1, r5) + dec_ref r1 + return r6 + +[case testBorrowedFinalAttributeChainedComparisonMerge] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(s: str) -> bool: + return s == "x" == C("x").s +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(s): + s, r0 :: str + r1, r2 :: bool + r3 :: str + r4 :: __main__.C + r5 :: str + r6 :: bool +L0: + r0 = 'x' + r1 = CPyStr_EqualLiteral(s, r0, 1) + if r1 goto L2 else goto L1 :: bool +L1: + r2 = r1 + goto L3 +L2: + r3 = 'x' + r4 = C(r3) + r5 = borrow r4.s + r6 = CPyStr_EqualLiteral(r5, r0, 1) + r2 = r6 + dec_ref r4 +L3: + return r2 + +[case testBorrowedFinalInListComprehension_withgil] +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(a: list[C]) -> list[str]: + return [x.s for x in a] +[out] +def C.__init__(self, s): + self :: __main__.C + s :: str +L0: + inc_ref s + self.s = s + return 1 +def f(a): + a :: list + r0 :: native_int + r1 :: list + r2, r3 :: native_int + r4 :: bit + r5 :: object + r6, x :: __main__.C + r7 :: str + r8 :: native_int +L0: + r0 = var_object_size a + r1 = PyList_New(r0) + r2 = 0 +L1: + r3 = var_object_size a + r4 = r2 < r3 :: signed + if r4 goto L2 else goto L4 :: bool +L2: + r5 = list_get_item_unsafe a, r2 + r6 = cast(__main__.C, r5) + x = r6 + r7 = x.s + dec_ref x + CPyList_SetItemUnsafe(r1, r2, r7) +L3: + r8 = r2 + 1 + r2 = r8 + goto L1 +L4: + return r1 + +[case testBorrowedFinalInSetComprehension_withgil] +from typing import Final + +class C: + def __init__(self, d: D) -> None: + self.d: Final = d + +class D: + def __init__(self, s: str) -> None: + self.s: Final = s + +def use(s: str) -> bool: + return True + +def f(a: list[C]) -> set[C]: + return {x for x in a if use(x.d.s)} +[out] +def C.__init__(self, d): + self :: __main__.C + d :: __main__.D +L0: + inc_ref d + self.d = d + return 1 +def D.__init__(self, s): + self :: __main__.D + s :: str +L0: + inc_ref s + self.s = s + return 1 +def use(s): + s :: str +L0: + return 1 +def f(a): + a :: list + r0 :: set + r1, r2 :: native_int + r3 :: bit + r4 :: object + r5, x :: __main__.C + r6 :: __main__.D + r7 :: str + r8 :: bool + r9 :: i32 + r10 :: bit + r11 :: native_int +L0: + r0 = PySet_New(0) + r1 = 0 +L1: + r2 = var_object_size a + r3 = r1 < r2 :: signed + if r3 goto L2 else goto L5 :: bool +L2: + r4 = list_get_item_unsafe a, r1 + r5 = cast(__main__.C, r4) + x = r5 + r6 = borrow x.d + r7 = borrow r6.s + r8 = use(r7) + if r8 goto L3 else goto L6 :: bool +L3: + r9 = PySet_Add(r0, x) + dec_ref x + r10 = r9 >= 0 :: signed +L4: + r11 = r1 + 1 + r1 = r11 + goto L1 +L5: + return r0 +L6: + dec_ref x + goto L4 + +[case testBorrowedFinalAttributeThroughCast] +from typing import Final, cast + +class C: + def __init__(self, x: object) -> None: + self.x: Final = x + +def make() -> object: + return C(1) + +def g(a: object) -> object: + return a + +def h() -> object: + return g(cast(C, make()).x) +[out] +def C.__init__(self, x): + self :: __main__.C + x :: object +L0: + inc_ref x + self.x = x + return 1 +def make(): + r0 :: object + r1 :: __main__.C +L0: + r0 = object 1 + r1 = C(r0) + return r1 +def g(a): + a :: object +L0: + inc_ref a + return a +def h(): + r0 :: object + r1 :: __main__.C + r2, r3 :: object +L0: + r0 = make() + r1 = borrow cast(__main__.C, r0) + r2 = borrow r1.x + r3 = g(r2) + dec_ref r0 + return r3 + +[case testBorrowedFinalAttributeCastChain] +from typing import Final, cast + +class Inner: + def __init__(self, x: object) -> None: + self.x: Final = x + +class Outer: + def __init__(self, inner: object) -> None: + self.inner: Final = inner + +def g(a: object) -> object: + return a + +def make() -> object: + return Outer(Inner(1)) + +def h() -> object: + # cast( Inner, ).x + o = cast(Outer, make()) + return g(cast(Inner, o.inner).x) +[out] +def Inner.__init__(self, x): + self :: __main__.Inner + x :: object +L0: + inc_ref x + self.x = x + return 1 +def Outer.__init__(self, inner): + self :: __main__.Outer + inner :: object +L0: + inc_ref inner + self.inner = inner + return 1 +def g(a): + a :: object +L0: + inc_ref a + return a +def make(): + r0 :: object + r1 :: __main__.Inner + r2 :: __main__.Outer +L0: + r0 = object 1 + r1 = Inner(r0) + r2 = Outer(r1) + dec_ref r1 + return r2 +def h(): + r0 :: object + r1, o :: __main__.Outer + r2 :: object + r3 :: __main__.Inner + r4, r5 :: object +L0: + r0 = make() + r1 = cast(__main__.Outer, r0) + o = r1 + r2 = borrow o.inner + r3 = borrow cast(__main__.Inner, r2) + r4 = borrow r3.x + r5 = g(r4) + dec_ref o + return r5 diff --git a/mypyc/test-data/run-async.test b/mypyc/test-data/run-async.test index 2733a31f3af2a..a7a08e7ced5f2 100644 --- a/mypyc/test-data/run-async.test +++ b/mypyc/test-data/run-async.test @@ -1416,6 +1416,7 @@ def run(x: object) -> object: ... [case testAsyncIntrospection] import asyncio +import gc import inspect import sys import weakref @@ -1423,6 +1424,8 @@ import weakref from functools import wraps from typing import Any, Callable, TypeVar, cast +from testutil import is_gil_disabled + def identity(val: int) -> int: return val @@ -1589,6 +1592,37 @@ def test_nested() -> None: assert is_coroutine(nested_wrapped_async) assert asyncio.run(nested_wrapped_async()) == 4 +def test_async_function_wrapper_code_refcount() -> None: + if is_gil_disabled(): + # On free-threaded builds the code object might be immortal, so the ref count test doesn't work. + return + code = getattr(identity_async, "__code__") + getrefcount = getattr(sys, "getrefcount") + # getrefcount sees the local code variable plus the wrapper-owned reference. + assert getrefcount(code) == 2, getrefcount(code) + +def test_nested_async_function_wrapper_code_refcount() -> None: + if is_gil_disabled(): + # On free-threaded builds the code object might be immortal, so the ref count test doesn't work. + return + def make_nested() -> Any: + async def nested_refcounted() -> int: + return 1 + + return nested_refcounted + + getrefcount = getattr(sys, "getrefcount") + fn = make_nested() + code = getattr(fn, "__code__") + before = getrefcount(code) + assert asyncio.run(fn()) == 1 + + del fn + gc.collect() + after = getrefcount(code) + assert before == after + 1, (before, after) + assert after == 1, after + [file asyncio/__init__.pyi] def run(x: object) -> object: ... @@ -1940,3 +1974,96 @@ async def async_iter(vals: list[int]) -> AsyncIterator[int]: yield v [typing fixtures/typing-full.pyi] + +[case testBorrowedFinalAttrAcrossAwait] +from typing import Final +from testutil import async_val + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +async def f(c: C) -> str: + # c.s is a final attr; it must not be borrowed across the await, which is a + # suspension point in the middle of the expression. + return c.s + await async_val("hello") + +[file driver.py] +from native import f, C +from testutil import run_generator + +for i in range(50): + c = C("value" + str(i)) + yields, val = run_generator(f(c), inputs=["world"]) + assert yields == ("hello",), yields + assert val == "value" + str(i) + "world", repr(val) + +[case testBorrowedFinalAttrAcrossAsyncComprehension] +import asyncio +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +class AsyncRange: + def __init__(self, n: int) -> None: + self.n = n + self.i = 0 + + def __aiter__(self) -> "AsyncRange": + return self + + async def __anext__(self) -> str: + if self.i >= self.n: + raise StopAsyncIteration + i = self.i + self.i += 1 + # Suspend in the middle of iteration. + await asyncio.sleep(0) + return "item" + str(i) + +async def f(c: C) -> str: + # c.s is a final attr; it must not be borrowed across the async comprehension, + # which suspends via an implicit await on __anext__ that isn't represented as + # an AwaitExpr node in the AST. + return c.s + "".join([x async for x in AsyncRange(3)]) + +async def test_async_comprehension_borrow() -> None: + for i in range(50): + c = C("value" + str(i)) + assert await f(c) == "value" + str(i) + "item0item1item2" + +[file asyncio/__init__.pyi] +async def sleep(t: float) -> None: ... + +[typing fixtures/typing-full.pyi] + +[case testBorrowedFinalAttrAcrossAwaitAfterComprehension] +import asyncio +from typing import Final + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +async def sink() -> str: + await asyncio.sleep(0) + return "z" + +async def f(c: C, xs: list[int]) -> str: + # A (sync) comprehension enters a nested borrow scope earlier in the same + # top-level expression. It must not clear the enclosing expression's suspend + # flag, or c.s (a final attr) gets borrowed across the await below, which + # produces invalid C (the borrow does not survive the suspension point). + return str([x for x in xs]) + (c.s + await sink()) + +async def test_borrow_final_attr_across_await_after_comprehension() -> None: + for i in range(50): + c = C("v" + str(i)) + assert await f(c, [1, 2, 3]) == "[1, 2, 3]" + "v" + str(i) + "z" + +[file asyncio/__init__.pyi] +async def sleep(t: float) -> None: ... + +[typing fixtures/typing-full.pyi] diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 39172a6385696..56ad3673e2896 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -1113,6 +1113,24 @@ assert f() == 10 A.x = 200 assert f() == 200 +[case testClassVarDoesNotShadowMethodGlobal] +from typing import ClassVar +from testutil import assertRaises + +class E(Exception): + pass + +class C: + E: ClassVar[type[E]] = E + + def m(self, b: bytes) -> None: + if not b: + raise E() + +def test_class_var_does_not_shadow_method_global() -> None: + with assertRaises(E): + C().m(b"") + [case testInitSubclassWithClassVar] from typing import ClassVar @@ -2807,6 +2825,98 @@ def test_final_attribute() -> None: assert C.b['x'] == 'y' assert C.a is C.b +[case testFinalInstanceAttributeCannotBeRebound] +from typing import Any, Final + +from testutil import assertRaises + +class C: + def __init__(self, x: int) -> None: + self.x: Final[int] = x + +class D(C): + def __init__(self, x: int, y: int) -> None: + super().__init__(x) + self.y: Final[int] = y + +def rebind_via_any(o: Any, value: int) -> None: + o.x = value + +def test_rebind_via_any() -> None: + c = C(1) + with assertRaises(AttributeError): + rebind_via_any(c, 2) + assert c.x == 1 + +def test_rebind_via_setattr() -> None: + c = C(1) + with assertRaises(AttributeError): + setattr(c, "x", 3) + assert c.x == 1 + +def test_rebind_inherited_via_setattr() -> None: + d = D(1, 2) + # Inherited Final attribute can't be modified. + with assertRaises(AttributeError): + setattr(d, "x", 3) + # The subclass's own Final attribute can't be modified either. + with assertRaises(AttributeError): + setattr(d, "y", 4) + assert d.x == 1 + assert d.y == 2 + +[case testFinalRefcountedAttributeRead] +# Exercises reading Final reference-counted ('PyObject *') attributes, which on +# free-threaded builds use a faster read path (CPy_GetAttrRefFinal) that skips the +# concurrent-writer guard, since a Final attribute is never rebound. +from typing import Final + +class C: + def __init__(self, s: str, items: list[int]) -> None: + self.s: Final = s + self.items: Final = items + + def read_s(self) -> str: + return self.s + + def read_items(self) -> list[int]: + return self.items + + def chained_len(self) -> int: + # borrowed intermediate read of a Final attr, then a call on it + return len(self.items) + +class D(C): + def __init__(self, s: str, items: list[int], t: str) -> None: + super().__init__(s, items) + self.t: Final = t + + def read_inherited(self) -> str: + return self.s + +def test_read_final_object_attrs() -> None: + c = C("hello", [1, 2, 3]) + # Read via getter (property access) and via native method bodies. + assert c.s == "hello" + assert c.read_s() == "hello" + assert c.items == [1, 2, 3] + assert c.read_items() == [1, 2, 3] + assert c.chained_len() == 3 + # Reading repeatedly must not corrupt the refcount / free the value. + for _ in range(1000): + assert c.read_s() == "hello" + assert c.read_items() == [1, 2, 3] + assert c.s == "hello" + assert c.read_items() is c.items + +def test_read_inherited_final_attr() -> None: + d = D("a", [4, 5], "b") + assert d.s == "a" + assert d.t == "b" + assert d.read_s() == "a" + assert d.read_inherited() == "a" + assert d.read_items() == [4, 5] + [case testClassDerivedFromIntEnum] from enum import IntEnum, auto @@ -5988,3 +6098,134 @@ for _ in range(100): check(foo) after = sys.getrefcount(foo.obj) assert after - init == 0, f"Leaked {after - init} refs" + +[case testNativeAttrSetterRefcountLeak] +# Setting a native attribute from interpreted code goes through the generated +# getset descriptor setter. For refcounted unboxed types (heap-boxed ints, +# tuples with refcounted items) the setter must take exactly one reference to +# the stored value, not two. +from dataclasses import dataclass +from typing import Tuple + +@dataclass +class IntField: + v: int + +@dataclass +class TupleField: + v: Tuple[int, int] + +[file driver.py] +import sys +from native import IntField, TupleField + +# A heap-boxed int (>= 2**62) is stored as a refcounted PyObject*, unlike small +# inline-tagged ints, so an over-incref strands a real reference. Compute the +# values at runtime (not as folded literals): on free-threaded builds code +# constants are immortal, and getrefcount could not observe a leak on them. +shift = 70 +BIG = 1 << shift + +def check_no_leak(make, value) -> None: + base = sys.getrefcount(value) + objs = [make(value) for _ in range(100)] + alive = sys.getrefcount(value) + # Each live instance must hold exactly one reference to the field value. + assert alive - base == 100, f"expected 100 live refs, got {alive - base}" + del objs + after = sys.getrefcount(value) + assert after == base, f"leaked {after - base} refs" + +# The dataclass-generated __init__ stores self.v = v via the descriptor setter. +check_no_leak(IntField, BIG) + +# Tuple[int, int] is stored as an unboxed RTuple; its boxed-int elements are +# refcounted, so the setter must not over-incref them either. Use the same int +# in both slots so each instance holds exactly two references to it. +ELEM = 1 << (shift + 1) +base = sys.getrefcount(ELEM) +objs = [TupleField((ELEM, ELEM)) for _ in range(100)] +alive = sys.getrefcount(ELEM) +assert alive - base == 200, f"expected 200 live refs, got {alive - base}" +del objs +assert sys.getrefcount(ELEM) == base, f"tuple field leaked {sys.getrefcount(ELEM) - base} refs" + +# Re-assigning through the setter must release the previous value too. +o = IntField(BIG) +base = sys.getrefcount(BIG) +o.v = BIG +o.v = BIG +assert sys.getrefcount(BIG) == base, "reassignment leaked refs" + +[case testBorrowedFinalAttributeInLambdaAndNestedFunction] +from typing import Final, Callable + +class Box: + def __init__(self, n: int) -> None: + self.n = n + +class C: + def __init__(self, b: Box) -> None: + self.b: Final = b + +def use(x: Box, y: Box) -> int: + return x.n + y.n + +def spend(x: Box) -> Box: + # Allocate a lot so a freed Box slot is likely to be reused, turning any + # use-after-free of a borrowed attribute into an observable wrong result. + junk = [Box(i) for i in range(100)] + return Box(x.n + junk[50].n - 50) + +def make_lambda() -> Callable[[], int]: + # The lambda body creates a C, borrows its final attribute, then calls + # spend() (which allocates heavily) before reading the borrow. + return lambda: use(C(Box(111)).b, spend(Box(0))) + +def make_nested() -> Callable[[], int]: + def inner() -> int: + return use(C(Box(111)).b, spend(Box(0))) + return inner + +def test_borrowed_final_attribute_in_lambda() -> None: + f = make_lambda() + for _ in range(1000): + assert f() == 111 + +def test_borrowed_final_attribute_in_nested_function() -> None: + g = make_nested() + for _ in range(1000): + assert g() == 111 + +[case testBorrowedFinalAttributeInComprehension] +from typing import Final + +class Box: + def __init__(self, n: int) -> None: + self.n = n + +class C: + def __init__(self, b: Box) -> None: + self.b: Final = b + +def use(x: Box, y: Box) -> int: + return x.n + y.n + +def spend(x: Box) -> Box: + # Allocate a lot so a freed Box slot is likely to be reused, turning any + # use-after-free of a borrowed attribute into an observable wrong result. + junk = [Box(i) for i in range(100)] + return Box(x.n + junk[50].n - 50) + +def comp(ns: list[int]) -> list[int]: + # Each iteration allocates a fresh C, borrows its final attribute .b, then + # calls spend() (which allocates heavily) before reading the borrow. The + # borrow container must be kept alive for the current iteration only; if it + # is scoped to the whole (comprehension) expression, the refcount pass + # dec_refs an uninitialized temporary on the loop back-edge and leaks/uses + # freed memory. + return [use(C(Box(111)).b, spend(Box(i))) for i in ns] + +def test_borrowed_final_attribute_in_comprehension() -> None: + for _ in range(1000): + assert comp([1, 2, 3, 4, 5]) == [112, 113, 114, 115, 116] diff --git a/mypyc/test-data/run-dunders.test b/mypyc/test-data/run-dunders.test index a3ec06763d75b..d5907d959ccb1 100644 --- a/mypyc/test-data/run-dunders.test +++ b/mypyc/test-data/run-dunders.test @@ -1009,3 +1009,15 @@ def test_equality_with_implicit_ne() -> None: assert not eq(Eq(1), Eq(2)) assert ne(Eq(1), Eq(2)) assert not ne(Eq(1), Eq(1)) + +[case testDundersImplicitNeWithNarrowRhs] +class Accepted: + pass + +class Eq: + def __eq__(self, other: Accepted) -> bool: # type: ignore[override] + return True + +def test_implicit_ne_with_narrow_rhs() -> None: + assert Eq() == Accepted() + assert not (Eq() != Accepted()) diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index a59d7f729e9fc..8932d0f110069 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1524,3 +1524,86 @@ def test_multiple_params() -> None: # Different parameter name than loop variable funcs2 = uses_multiple_params_different_name(["a", "b"], "!") assert [f() for f in funcs2] == ["a!", "b!"] + +[case testConcurrentFirstCallWithKeywordArgs] +# Regression test for a free-threading data race in argument parser +# initialization. The first call to a compiled function with keyword arguments +# lazily initializes a static CPyArg_Parser and pushes it onto a global list. +# When many threads race that first call on a free-threaded build, the +# initialization and list insertion must be synchronized, or the runtime hits +# a failed assertion (parser->next == NULL) and aborts. +import sys +import threading + +# A pool of distinct functions, none of which is called before the concurrent +# test. Each function's first call lazily initializes a static CPyArg_Parser, +# and the test races that initialization across threads. Keyword arguments +# force the call through the slow path that runs parser_init. Using many +# functions gives many independent races per run, so the test reliably triggers +# the bug on an unfixed free-threaded build instead of depending on a single +# narrow timing window. +def g0(a: int, b: int, c: int) -> int: return a + b + c +def g1(a: int, b: int, c: int) -> int: return a + b + c +def g2(a: int, b: int, c: int) -> int: return a + b + c +def g3(a: int, b: int, c: int) -> int: return a + b + c +def g4(a: int, b: int, c: int) -> int: return a + b + c +def g5(a: int, b: int, c: int) -> int: return a + b + c +def g6(a: int, b: int, c: int) -> int: return a + b + c +def g7(a: int, b: int, c: int) -> int: return a + b + c +def g8(a: int, b: int, c: int) -> int: return a + b + c +def g9(a: int, b: int, c: int) -> int: return a + b + c +def g10(a: int, b: int, c: int) -> int: return a + b + c +def g11(a: int, b: int, c: int) -> int: return a + b + c +def g12(a: int, b: int, c: int) -> int: return a + b + c +def g13(a: int, b: int, c: int) -> int: return a + b + c +def g14(a: int, b: int, c: int) -> int: return a + b + c +def g15(a: int, b: int, c: int) -> int: return a + b + c +def g16(a: int, b: int, c: int) -> int: return a + b + c +def g17(a: int, b: int, c: int) -> int: return a + b + c +def g18(a: int, b: int, c: int) -> int: return a + b + c +def g19(a: int, b: int, c: int) -> int: return a + b + c +def g20(a: int, b: int, c: int) -> int: return a + b + c +def g21(a: int, b: int, c: int) -> int: return a + b + c +def g22(a: int, b: int, c: int) -> int: return a + b + c +def g23(a: int, b: int, c: int) -> int: return a + b + c +def g24(a: int, b: int, c: int) -> int: return a + b + c +def g25(a: int, b: int, c: int) -> int: return a + b + c +def g26(a: int, b: int, c: int) -> int: return a + b + c +def g27(a: int, b: int, c: int) -> int: return a + b + c +def g28(a: int, b: int, c: int) -> int: return a + b + c +def g29(a: int, b: int, c: int) -> int: return a + b + c + +FUNCS = [g0, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, + g15, g16, g17, g18, g19, g20, g21, g22, g23, g24, g25, g26, g27, + g28, g29] + +def is_gil_disabled() -> bool: + return hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled() + +def test_concurrent_first_call() -> None: + if not is_gil_disabled(): + # The race can only happen without the GIL. + return + + num_threads = 16 + barrier = threading.Barrier(num_threads) + errors: list[str] = [] + + def run() -> None: + # Line up all threads, then let them race freely through the list. The + # first call to each function lazily initializes its parser, so every + # function is a fresh race under real parallel pressure. + barrier.wait() + try: + for fn in FUNCS: + assert fn(a=1, b=2, c=3) == 6 + except BaseException as e: + errors.append(repr(e)) + + threads = [threading.Thread(target=run) for _ in range(num_threads)] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not errors, errors diff --git a/mypyc/test-data/run-generators.test b/mypyc/test-data/run-generators.test index cf1dac7c57333..a23af2ed6eea7 100644 --- a/mypyc/test-data/run-generators.test +++ b/mypyc/test-data/run-generators.test @@ -944,3 +944,46 @@ from typing import Optional, Union def test_compiledGeneratorEmptyTuple() -> None: jobs: Generator[Optional[str], None, None] = (_ for _ in ()) assert list(jobs) == [] + +[case testBorrowedFinalAttrAcrossYield] +from typing import Final, Generator + +class C: + def __init__(self, s: str) -> None: + self.s: Final = s + +def f(c: C) -> Generator[str, str, None]: + # c.s is a final attr; it must not be borrowed across the yield, which is + # a suspension point in the middle of the expression. + x = c.s + (yield "first") + yield x + +def test_borrow_across_yield() -> None: + for i in range(50): + c = C("value" + str(i)) + g = f(c) + assert next(g) == "first" + assert g.send("world") == "value" + str(i) + "world" + +def yield_from_gen(c: C) -> Generator[str, str, str]: + r = yield "inner" + return c.s + r + +def g2(c: C) -> Generator[str, str, str]: + # Borrow a final attr across a yield from expression. + x = c.s + (yield from yield_from_gen(c)) + return x + +def test_borrow_across_yield_from() -> None: + for i in range(50): + c = C("v" + str(i)) + gen = g2(c) + assert next(gen) == "inner" + try: + gen.send("!") + except StopIteration as e: + assert e.value == "v" + str(i) + "v" + str(i) + "!", e.value + else: + assert False + +[typing fixtures/typing-full.pyi] diff --git a/mypyc/test-data/run-librt-random.test b/mypyc/test-data/run-librt-random.test new file mode 100644 index 0000000000000..0b34222678018 --- /dev/null +++ b/mypyc/test-data/run-librt-random.test @@ -0,0 +1,344 @@ +[case testRandom_librt] +from typing import Any + +from librt.random import Random, random, randint, randrange, seed +from mypy_extensions import i64 +from testutil import assertRaises + +# +# Random object basics +# + +def test_random_construct() -> None: + r = Random() + assert isinstance(r, Random) + +def test_randint_basic() -> None: + r = Random() + for i in range(100): + val = r.randint(0, 10) + assert 0 <= val <= 10 + +def test_randint_single_value() -> None: + r = Random() + for i in range(10): + assert r.randint(5, 5) == 5 + +def test_randint_negative_range() -> None: + r = Random() + for i in range(100): + val = r.randint(-10, -1) + assert -10 <= val <= -1 + +def test_randint_mixed_range() -> None: + r = Random() + for i in range(100): + val = r.randint(-5, 5) + assert -5 <= val <= 5 + +def test_randint_large_range() -> None: + r = Random() + for i in range(100): + val = r.randint(0, 1000000) + assert 0 <= val <= 1000000 + +def test_randint_produces_different_values() -> None: + r = Random() + values = set() + for i in range(100): + values.add(r.randint(0, 1000000)) + # With range 0-1000000 and 100 samples, we should get at least 2 distinct values + assert len(values) > 1 + +def test_random_basic() -> None: + r = Random() + for i in range(100): + val = r.random() + assert 0.0 <= val < 1.0 + +def test_random_returns_float() -> None: + r = Random() + val = r.random() + assert isinstance(val, float) + +def test_random_produces_different_values() -> None: + r = Random() + values = set() + for i in range(100): + values.add(r.random()) + assert len(values) > 1 + +def test_randrange_one_arg() -> None: + r = Random() + for i in range(100): + val = r.randrange(10) + assert 0 <= val < 10 + +def test_randrange_two_args() -> None: + r = Random() + for i in range(100): + val = r.randrange(5, 15) + assert 5 <= val < 15 + +def test_randrange_negative() -> None: + r = Random() + for i in range(100): + val = r.randrange(-10, 0) + assert -10 <= val < 0 + +def test_randrange_single_value() -> None: + r = Random() + for i in range(10): + assert r.randrange(7, 8) == 7 + +def test_randrange_produces_different_values() -> None: + r = Random() + values = set() + for i in range(100): + values.add(r.randrange(1000000)) + assert len(values) > 1 + +def test_constructor_seed() -> None: + r1 = Random(42) + r2 = Random(42) + vals1 = [r1.randint(0, 1000000) for _ in range(20)] + vals2 = [r2.randint(0, 1000000) for _ in range(20)] + assert vals1 == vals2 + +def test_constructor_seed_different() -> None: + r1 = Random(42) + r2 = Random(43) + vals1 = [r1.randint(0, 1000000) for _ in range(20)] + vals2 = [r2.randint(0, 1000000) for _ in range(20)] + assert vals1 != vals2 + +def test_constructor_none_seed() -> None: + r = Random(None) + val = r.random() + assert 0.0 <= val < 1.0 + +def test_seed_method() -> None: + r = Random(0) + r.seed(42) + vals1 = [r.randint(0, 1000000) for _ in range(20)] + r.seed(42) + vals2 = [r.randint(0, 1000000) for _ in range(20)] + assert vals1 == vals2 + +def test_seed_method_resets_state() -> None: + r = Random(42) + expected = [r.randint(0, 1000000) for _ in range(20)] + # Consume some values, then reseed + r.seed(42) + actual = [r.randint(0, 1000000) for _ in range(20)] + assert expected == actual + +# +# Module-level functions +# + +def test_module_random_basic() -> None: + for i in range(100): + val = random() + assert 0.0 <= val < 1.0 + +def test_module_random_returns_float() -> None: + assert isinstance(random(), float) + +def test_module_random_produces_different_values() -> None: + values = set() + for i in range(100): + values.add(random()) + assert len(values) > 1 + +def test_module_randint_basic() -> None: + for i in range(100): + val = randint(0, 10) + assert 0 <= val <= 10 + +def test_module_randint_single_value() -> None: + for i in range(10): + assert randint(5, 5) == 5 + +def test_module_randint_produces_different_values() -> None: + values = set() + for i in range(100): + values.add(randint(0, 1000000)) + assert len(values) > 1 + +def test_module_randrange_one_arg() -> None: + for i in range(100): + val = randrange(10) + assert 0 <= val < 10 + +def test_module_randrange_two_args() -> None: + for i in range(100): + val = randrange(5, 15) + assert 5 <= val < 15 + +def test_module_randrange_produces_different_values() -> None: + values = set() + for i in range(100): + values.add(randrange(1000000)) + assert len(values) > 1 + +def test_module_seed_reproducible() -> None: + seed(42) + vals1 = [randint(0, 1000000) for _ in range(20)] + seed(42) + vals2 = [randint(0, 1000000) for _ in range(20)] + assert vals1 == vals2 + +def test_module_seed_different() -> None: + seed(42) + vals1 = [randint(0, 1000000) for _ in range(20)] + seed(43) + vals2 = [randint(0, 1000000) for _ in range(20)] + assert vals1 != vals2 + +# +# Wrapper function calling convention (via Any) +# + +def test_method_random_via_wrapper() -> None: + r: Any = Random(42) + val = r.random() + assert isinstance(val, float) + assert 0.0 <= val < 1.0 + +def test_method_seed_via_wrapper() -> None: + r: Any = Random(0) + r.seed(42) + val = r.random() + assert 0.0 <= val < 1.0 + +def test_module_random_via_wrapper() -> None: + random_any: Any = random + val = random_any() + assert isinstance(val, float) + assert 0.0 <= val < 1.0 + +def test_module_randint_via_wrapper() -> None: + randint_any: Any = randint + val = randint_any(0, 10) + assert 0 <= val <= 10 + +def test_module_seed_via_wrapper() -> None: + seed_any: Any = seed + seed_any(42) + +# +# Wide i64 ranges +# + +def method_randint(r: Random, a: i64, b: i64) -> i64: + return r.randint(a, b) + +def method_randrange(r: Random, a: i64, b: i64) -> i64: + return r.randrange(a, b) + +def module_randint(a: i64, b: i64) -> i64: + return randint(a, b) + +def module_randrange(a: i64, b: i64) -> i64: + return randrange(a, b) + +def test_full_i64_randint_native() -> None: + lo: i64 = -9223372036854775808 + hi: i64 = 9223372036854775807 + r = Random(42) + saw_non_min = False + for i in range(20): + val = method_randint(r, lo, hi) + assert lo <= val <= hi + if val != lo: + saw_non_min = True + assert saw_non_min + +def test_full_i64_randint_module_native() -> None: + lo: i64 = -9223372036854775808 + hi: i64 = 9223372036854775807 + saw_non_min = False + for i in range(20): + val = module_randint(lo, hi) + assert lo <= val <= hi + if val != lo: + saw_non_min = True + assert saw_non_min + +def test_wide_i64_randrange_native() -> None: + lo: i64 = -9223372036854775808 + hi: i64 = 9223372036854775807 + r = Random(43) + for i in range(20): + val = method_randrange(r, lo, hi) + assert lo <= val < hi + val = module_randrange(lo, hi) + assert lo <= val < hi + +def test_full_i64_randint_python_api() -> None: + r: Any = Random(42) + lo = -9223372036854775808 + hi = 9223372036854775807 + saw_non_min = False + for i in range(20): + val = r.randint(lo, hi) + assert lo <= val <= hi + if val != lo: + saw_non_min = True + assert saw_non_min + +def test_wide_i64_randrange_python_api() -> None: + r: Any = Random(43) + randrange_any: Any = randrange + lo = -9223372036854775808 + hi = 9223372036854775807 + for i in range(20): + val = r.randrange(lo, hi) + assert lo <= val < hi + val = randrange_any(lo, hi) + assert lo <= val < hi + +# +# Error handling +# + +def test_randint_empty_range() -> None: + r = Random() + with assertRaises(ValueError, "empty range"): + r.randint(10, 5) + +def test_randint_wrong_arg_count() -> None: + r = Random() + with assertRaises(TypeError): + r.randint(1) # type: ignore[call-arg] + with assertRaises(TypeError): + r.randint(1, 2, 3) # type: ignore[call-arg] + +def test_module_randint_empty_range() -> None: + with assertRaises(ValueError, "empty range"): + randint(10, 5) + +def test_randrange_empty_range() -> None: + r = Random() + with assertRaises(ValueError, "empty range"): + r.randrange(0) + with assertRaises(ValueError, "empty range"): + r.randrange(-5) + with assertRaises(ValueError, "empty range"): + r.randrange(10, 10) + with assertRaises(ValueError, "empty range"): + r.randrange(10, 5) + +def test_randrange_wrong_arg_count() -> None: + r = Random() + with assertRaises(TypeError): + r.randrange() # type: ignore[call-overload] + with assertRaises(TypeError): + r.randrange(1, 2, 3) # type: ignore[call-overload] + +def test_module_randrange_empty_range() -> None: + with assertRaises(ValueError, "empty range"): + randrange(0) + with assertRaises(ValueError, "empty range"): + randrange(10, 5) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 909766d5c8e74..333b5962c4c99 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -1439,3 +1439,113 @@ def test_new_without_init_is_usable() -> None: assert sw.getvalue() == "" sw.write("hello") assert sw.getvalue() == "hello" + +[case testLibrtStringsCodepointClassifiers_librt] +from typing import Any +from mypy_extensions import i32 +from librt.strings import isspace, isdigit, isalnum, isalpha, isidentifier + +from testutil import assertRaises + + +def test_codepoint_classifiers() -> None: + # Out-of-range values are not codepoints: negative, just past the maximum + # valid code point (0x10FFFF), and the largest i32. + for bad in (i32(-1), i32(-113), i32(0x110000), i32(0x7FFFFFFF)): + assert not isspace(bad) + assert not isdigit(bad) + assert not isalnum(bad) + assert not isalpha(bad) + assert not isidentifier(bad) + # Verify each codepoint primitive agrees with the matching str method + # across all Unicode codepoints, including the ord(chr(i)) round-trip. + # Any forces generic dispatch on the str side. + for i in range(0x110000): + c = chr(i) + a: Any = c + o = ord(c) + assert isspace(o) == isspace(i) == a.isspace() + assert isdigit(o) == isdigit(i) == a.isdigit() + assert isalnum(o) == isalnum(i) == a.isalnum() + assert isalpha(o) == isalpha(i) == a.isalpha() + assert isidentifier(o) == isidentifier(i) == a.isidentifier() + + +def test_codepoint_classifiers_via_any() -> None: + # Iterate so the callee is opaque to mypyc and dispatch falls back to + # the PyMethodDef wrapper, exercising the i32 range check. + for fn, true_input, false_input in ( + (isspace, " ", "a"), + (isdigit, "5", "a"), + (isalnum, "A", " "), + (isalpha, "A", " "), + (isidentifier, "A", "0"), + ): + f: Any = fn + assert f(ord(true_input)) is True + assert f(ord(false_input)) is False + # Negative values are valid i32, just not codepoints. + assert f(-1) is False + # Values within i32 range but past the maximum code point (0x10FFFF) + # are not codepoints either. + assert f(0x110000) is False + assert f(0x7FFFFFFF) is False + # Inputs outside i32 range raise OverflowError through the wrapper. + with assertRaises(OverflowError, "codepoint out of i32 range"): + f(1 << 40) + with assertRaises(OverflowError, "codepoint out of i32 range"): + f(-(1 << 40)) + +[case testLibrtStringsCodepointCaseConversion_librt] +from typing import Any +from mypy_extensions import i32 +from librt.strings import toupper, tolower + +from testutil import assertRaises + + +def _expect(c: str, method: str) -> int: + # The contract: i32 -> i32 when conversion yields exactly one codepoint, + # else return the input unchanged. + converted = getattr(c, method)() + if len(converted) == 1: + return ord(converted) + return ord(c) + + +def test_codepoint_case_conversion() -> None: + # Out-of-range inputs return unchanged: negative, just past the maximum + # valid code point (0x10FFFF), and the largest i32. + for bad in (i32(-1), i32(-113), i32(0x110000), i32(0x7FFFFFFF)): + assert toupper(bad) == bad + assert tolower(bad) == bad + # Agree with str.upper / str.lower across the full Unicode range + # whenever the conversion is single-codepoint; otherwise return input. + for i in range(0x110000): + c = chr(i) + o = ord(c) + assert toupper(o) == _expect(c, "upper") + assert tolower(o) == _expect(c, "lower") + + +def test_codepoint_case_conversion_via_any() -> None: + # Iterate to force generic dispatch through the PyMethodDef wrapper. + for fn, in_cp, out_cp in ( + (toupper, ord("a"), ord("A")), + (toupper, ord("A"), ord("A")), + (tolower, ord("Z"), ord("z")), + (tolower, ord("z"), ord("z")), + ): + f: Any = fn + assert f(in_cp) == out_cp + # Negative values are valid i32, returned unchanged. + assert f(-1) == -1 + # Values within i32 range but past the maximum code point (0x10FFFF) + # are returned unchanged. + assert f(0x110000) == 0x110000 + assert f(0x7FFFFFFF) == 0x7FFFFFFF + # Inputs outside i32 range raise OverflowError through the wrapper. + with assertRaises(OverflowError, "codepoint out of i32 range"): + f(1 << 40) + with assertRaises(OverflowError, "codepoint out of i32 range"): + f(-(1 << 40)) diff --git a/mypyc/test-data/run-lists.test b/mypyc/test-data/run-lists.test index 40ca1b6e005f9..d06fc9ce10b0b 100644 --- a/mypyc/test-data/run-lists.test +++ b/mypyc/test-data/run-lists.test @@ -171,6 +171,68 @@ def test_list_build() -> None: l3.append('a') assert l3 == ['a'] +class C: + def __init__(self, s: str) -> None: + self.s = s + +a = [C("a"), C("b"), C("c")] + +def test_get_item() -> None: + zero = int() + assert a[zero].s == "a" + assert a[zero + 1].s == "b" + assert a[zero + 2].s == "c" + assert a[zero - 1].s == "c" + assert a[zero - 2].s == "b" + assert a[zero - 3].s == "a" + with assertRaises(IndexError): + a[zero + 3] + with assertRaises(IndexError): + a[zero - 4] + # TODO: Raise IndexError? + with assertRaises(OverflowError): + a[zero + 2**90] + with assertRaises(OverflowError): + a[zero - 2**90] + +def test_get_item_literal() -> None: + assert a[0].s == "a" + assert a[1].s == "b" + assert a[2].s == "c" + assert a[-1].s == "c" + assert a[-2].s == "b" + assert a[-3].s == "a" + with assertRaises(IndexError): + a[3] + with assertRaises(IndexError): + a[-4] + # TODO: Raise IndexError? + with assertRaises(OverflowError): + a[2**90] + with assertRaises(OverflowError): + a[-2**90] + +def test_set_item() -> None: + l = a.copy() + zero = int() + zero2 = int() + l[zero] = C("x") + assert l[zero2].s == "x" + l[zero + 2] = C("y") + assert l[zero2 + 2].s == "y" + l[zero - 1] = C("t") + assert l[zero2 - 1].s == "t" + l[zero - 3] = C("u") + assert l[zero2 - 3].s == "u" + with assertRaises(IndexError): + a[zero + 3] = C("z") + with assertRaises(IndexError): + a[zero - 4] = C("z") + with assertRaises(OverflowError): + a[zero + 2**90] = C("z") + with assertRaises(OverflowError): + a[zero - 2**90] = C("z") + def test_append() -> None: l = [1, 2] l.append(10) diff --git a/mypyc/test-data/run-misc.test b/mypyc/test-data/run-misc.test index eda44e16871f2..d48884ada853a 100644 --- a/mypyc/test-data/run-misc.test +++ b/mypyc/test-data/run-misc.test @@ -971,7 +971,10 @@ print(z) [case testCheckVersion] import sys -if sys.version_info[:2] == (3, 15): +if sys.version_info[:2] == (3, 16): + def version() -> int: + return 16 +elif sys.version_info[:2] == (3, 15): def version() -> int: return 15 elif sys.version_info[:2] == (3, 14): diff --git a/mypyc/test-data/run-multimodule.test b/mypyc/test-data/run-multimodule.test index 3ab589ab1530a..4cf391d312dff 100644 --- a/mypyc/test-data/run-multimodule.test +++ b/mypyc/test-data/run-multimodule.test @@ -1246,6 +1246,39 @@ import native [rechecked other_a] +-- Test that importing a skipped module does not force rechecks. +[case testIncrementalCompilationFollowImportsSkip] +from other_dep import f +assert f() == 1 + +[file other_dep.py] +import skipped + +def f() -> int: + return 1 + +def g() -> int: + return 2 + +# Files under "skipped" are not typechecked because of the "--follow_imports = skip" option. +[file skipped/__init__.py] +x = 1 + +[file skipped/other_child.py] +import skipped + +def child() -> int: + return 1 + +[file native.py.2] +from other_dep import g +assert g() == 2 + +[file driver.py] +import native + +[rechecked native] + [case testSeparateCompilationWithUndefinedAttribute] from other_a import A @@ -1391,6 +1424,60 @@ def translate(b: bytes) -> bytes: import native assert native.translate(b'ABCD') == b'BBCD' +[case testIncrementalCrossGroupExportTableOffsets] +# Regression: under separate=True, each consumer module's IR is +# compiled against the positional layout of its deps' +# `exports_` struct. Reordering the dep's classes keeps the +# same set of public names, so mypy's interface hash for the dep is +# unchanged -- the consumer is not invalidated and stays fully +# cached, which causes `_load_cached_group_files` to return empty +# cfile content for the consumer's group. +# +# Before the fix, `get_header_deps` over empty content returned no +# includes, so `Extension.depends` for the consumer ended up empty +# and setuptools never recompiled the consumer's .o when the dep's +# `__native_.h` shifted struct offsets. The stale .o kept the +# old offsets and silently resolved cross-group calls to the wrong +# class. +from other_classes import Gamma, Delta + +def make_gamma() -> Gamma: + return Gamma() + +def make_delta() -> Delta: + return Delta() + +[file other_classes.py] +class Alpha: + a: int = 1 + +class Beta: + b: int = 2 + +class Gamma: + g: int = 3 + +class Delta: + d: int = 4 + +[file other_classes.py.2] +class Delta: + d: int = 4 + +class Alpha: + a: int = 1 + +class Beta: + b: int = 2 + +class Gamma: + g: int = 3 + +[file driver.py] +import native +assert type(native.make_gamma()).__name__ == "Gamma" +assert type(native.make_delta()).__name__ == "Delta" + [case testCrossModuleAttrDefaults] from other import Parent @@ -1423,6 +1510,45 @@ class Parent: from native import test test() +[case testIncrementalCrossModuleInheritedAttrDefaults] +# Regression: under separate=True, when only the subclass module is +# recompiled (parent loaded from mypy's incremental cache, so its +# ClassDef.defs.body is empty), the subclass produces no +# __mypyc_defaults_setup of its own and ClassIR.get_method returns +# the parent's. The emitted call must use the cross-group +# exports_. prefix, otherwise the generated C references an +# undeclared symbol and clang/gcc fail to compile. +import other_a + +def test() -> None: + c = other_a.Child() + assert c.x == 1 + assert c.y == "hello" + +[file other_b.py] +class Parent: + x: int = 1 + y: str = "hello" + +[file other_a.py] +from other_b import Parent + +class Child(Parent): + pass + +[file other_a.py.2] +from other_b import Parent + +class Child(Parent): + pass + +def _force_recompile() -> int: + return 1 + +[file driver.py] +from native import test +test() + [case testExtraOpsFunction_experimental_librt] from librt.strings import BytesWriter @@ -1617,6 +1743,41 @@ assert sum_range(0) == 0 assert sum_range(1) == 0 assert sum_range(5) == 10 +[case testPackageCycleInheritedEq] +-- Regression test for generating __ne__ from an inherited __eq__ when the +-- inherited method's declaration is available before its FuncIR body has been +-- generated. This can happen in package import cycles where __init__ imports a +-- child module before the subpackage that defines the base class. +from other.other_child import Child + +def make_child() -> Child: + return Child() + +[file other/__init__.py] +from other.other_child import Child as Child +from other.other_subpkg import Base as Base + +[file other/other_child.py] +from other.other_subpkg import Base + +class Child(Base): + pass + +[file other/other_subpkg/__init__.py] +from other.other_subpkg.other_base import Base as Base + +[file other/other_subpkg/other_base.py] +class Base: + def __eq__(self, other: object) -> bool: + return True + +[file driver.py] +from native import make_child +left = make_child() +right = make_child() +assert left == right +assert not (left != right) + [case testSeparateCrossGroupInheritedInit] -- Under separate=True, a subclass whose __init__ is inherited from a -- different group must call the base's CPyPy_ wrapper through the exports @@ -1641,3 +1802,130 @@ class Base: from native import make_child assert make_child(7) == "child(7)" assert make_child(-1) == "child(-1)" + +[case testIncrementalBuiltinBaseClassConstruction] +# Regression: builtin_base classes (Exception subclasses) were unconditionally +# added to func_to_decl in load_type_map, causing cross-group call sites to +# emit CPyDef instead of CPyType for the constructor. +from other_errors import MyError +from other_util import process + +def run(value: str) -> None: + if not value: + raise MyError("empty") + +def compute(x: str) -> str: + result = process(x) + if not result: + raise MyError("no result") + return result + +[file other_errors.py] +class MyError(Exception): + pass + +[file other_util.py] +def process(x: str) -> str: + return x + +[file other_util.py.2] +def process(x: str, flag: bool = False) -> str: + return x.strip() + +[file driver.py] +from native import run, compute +try: + run("") +except Exception as e: + print(str(e)) +print(compute("hello")) + +[out] +empty +hello +[out2] +empty +hello + +[case testIncrementalCrossModuleInheritedAttrDefaultsWithOverride] +# Regression: same shape as testIncrementalCrossModuleInheritedAttrDefaults, +# but the subclass adds an attribute of its own, so generate_attr_defaults_init +# emits a __mypyc_defaults_setup for it. Before the fix, the recompiled +# subclass walked the parent's ClassDef.defs.body to collect inherited +# defaults; when the parent was loaded from mypy's incremental cache that +# body was empty, so the inherited initialization was dropped and any +# access to an inherited attribute through compiled code raised +# "AttributeError: attribute '' of '' undefined". +import other_a + +def test() -> None: + c = other_a.Child() + # Inherited attributes must still be initialized after the subclass + # has been recompiled against a cache-loaded parent. + assert c.x == 1 + assert c.y == "hello" + # Own override is set by the subclass's own __mypyc_defaults_setup. + assert c.z is True + # Method defined on the parent reads an inherited attribute through + # the compiled path; this is what crashes pre-fix. + assert c.use() == 1 + +[file other_b.py] +class Parent: + x: int = 1 + y: str = "hello" + z: bool = False + + def use(self) -> int: + if self.x: + return 1 + return 0 + +[file other_a.py] +from other_b import Parent + +class Child(Parent): + z: bool = True + +[file other_a.py.2] +from other_b import Parent + +class Child(Parent): + z: bool = True + +def _force_recompile() -> int: + return 1 + +[file driver.py] +from native import test +test() + +[case testCrossModuleInheritedAttrDefaultsSameGroup] +# separate: [(["native.py"], "grp1"), (["other_a.py", "other_b.py"], "grp2")] +# Regression: with the subclass (other_a) and base (other_b) in the same +# compilation group, IR build runs alphabetically within the group, so +# the subclass is IR-built before the base. The decision to emit +# __mypyc_defaults_setup (and a chained call to the ancestor's) must be +# set up in the prepare phase, before any IR build runs; otherwise the +# subclass's lookup of the parent's setup decl misses and inherited +# defaults are lost on a fresh build. +import other_a + +def test() -> None: + c = other_a.Child() + assert c.x == 1 + assert c.z is True + +[file other_b.py] +class Parent: + x: int = 1 + +[file other_a.py] +from other_b import Parent + +class Child(Parent): + z: bool = True + +[file driver.py] +from native import test +test() diff --git a/mypyc/test-data/run-python312.test b/mypyc/test-data/run-python312.test index 5ed6dca9ecb28..f3dc272fc9fa1 100644 --- a/mypyc/test-data/run-python312.test +++ b/mypyc/test-data/run-python312.test @@ -229,9 +229,10 @@ type C[*Ts] = tuple[*Ts] def test_type_var_tuple_type_alias() -> None: if sys.version_info >= (3, 15): # type: ignore[operator] assert str(C[int, str]) == "_frozen_importlib.C[int, str]" + assert str(getattr(C, "__value__")) == "tuple[typing.Unpack[~Ts]]" else: assert str(C[int, str]) == "C[int, str]" - assert str(getattr(C, "__value__")) == "tuple[typing.Unpack[Ts]]" + assert str(getattr(C, "__value__")) == "tuple[typing.Unpack[Ts]]" type D[**P] = Callable[P, int] diff --git a/mypyc/test-data/run-strings.test b/mypyc/test-data/run-strings.test index ec662da969865..81b85580d7a7b 100644 --- a/mypyc/test-data/run-strings.test +++ b/mypyc/test-data/run-strings.test @@ -1412,3 +1412,64 @@ def test_isdigit_strings() -> None: assert not "\u00e9\u00e8".isdigit() assert not "123\u00e9".isdigit() assert not "\U0001d7ce!".isdigit() + +[case testStrIndexEqLiteralSpecialize] +from testutil import assertRaises + +# The specializer fires on the AST shape `IndexExpr == StrLiteral` (or the +# symmetric swap, and `!=`). The literal has to be a real source-level +# string literal (can't be passed in as a parameter), so each test +# function pins one distinct shape. + +def eq_comma(s: str, i: int) -> bool: + # Specialized: s[i] == "x". + return s[i] == "," + +def ne_comma(s: str, i: int) -> bool: + # Specialized: s[i] != "x". + return s[i] != "," + +def comma_eq(s: str, i: int) -> bool: + # Specialized: "x" == s[i]. Operand-swap is normalized. + return "," == s[i] + +def eq_two_chars(s: str, i: int) -> bool: + # Not specialized: literal isn't 1 char. Falls through to the generic + # str compare, which returns False since s[i] is always 1 codepoint. + return s[i] == "ab" + +def eq_empty(s: str, i: int) -> bool: + # Not specialized: empty literal. Same fall-through. + return s[i] == "" + +def test_specialized_path() -> None: + s = "a,b" # comma at index 1 + assert eq_comma(s, 1) + assert not eq_comma(s, 0) + assert not eq_comma(s, 2) + # != inverts. + assert ne_comma(s, 0) + assert not ne_comma(s, 1) + # Literal on the LHS is normalized to the same shape. + assert comma_eq(s, 1) + assert not comma_eq(s, 0) + +def test_negative_index_is_adjusted() -> None: + s = "a,b" + assert eq_comma(s, -2) # -2 -> 1 (',') + assert not eq_comma(s, -1) # -1 -> 2 ('b') + +def test_non_1char_literal_falls_through() -> None: + s = "a,b" + # Generic str compare answers False because s[i] has length 1. + assert not eq_two_chars(s, 0) + assert not eq_two_chars(s, 1) + assert not eq_empty(s, 0) + +def test_out_of_range_raises_indexerror() -> None: + # Bounds-check semantics match the unspecialized s[i] path. + s = "a,b" + with assertRaises(IndexError): + eq_comma(s, 3) + with assertRaises(IndexError): + eq_comma(s, -4) diff --git a/mypyc/test-data/run-threading.test b/mypyc/test-data/run-threading.test new file mode 100644 index 0000000000000..8af8755b76c44 --- /dev/null +++ b/mypyc/test-data/run-threading.test @@ -0,0 +1,156 @@ +# Test cases for librt.threading (compile and run) + +[case testLockBasics_librt] +from typing import Any +from testutil import assertRaises +from librt.threading import Lock + +class BadBool: + def __bool__(self) -> bool: + raise RuntimeError("bad bool") + +def test_lock_basic() -> None: + lock = Lock() + assert not lock.locked() + assert lock.acquire() + assert lock.locked() + lock.release() + assert not lock.locked() + +def test_lock_context_manager() -> None: + lock = Lock() + with lock as acquired: + assert acquired is True + assert lock.locked() + assert not lock.locked() + +def test_lock_non_blocking() -> None: + lock = Lock() + assert lock.acquire() + assert not lock.acquire(False) + lock.release() + assert lock.acquire(False) + lock.release() + +def test_contention() -> None: + import threading + lock = Lock() + counter = [0] + n_threads = 4 + n_increments = 10000 + + def worker() -> None: + for _ in range(n_increments): + lock.acquire() + counter[0] += 1 + lock.release() + + threads = [threading.Thread(target=worker) for _ in range(n_threads)] + for t in threads: + t.start() + for t in threads: + t.join() + assert counter[0] == n_threads * n_increments + +def test_cross_thread_release() -> None: + # threading.Lock is unowned: a lock acquired on one thread may be + # released from another, after which another thread can acquire it. + import threading + lock = Lock() + assert lock.acquire() + + started = threading.Event() + released = threading.Event() + + def releaser() -> None: + started.set() + # Hand the lock off from a different thread than the acquirer. + lock.release() + released.set() + + t = threading.Thread(target=releaser) + t.start() + started.wait() + # This may block until releaser releases the lock on the other thread. + assert lock.acquire() + t.join() + assert released.is_set() + lock.release() + assert not lock.locked() + +def test_context_manager_exception() -> None: + lock = Lock() + try: + with lock: + assert lock.locked() + raise ValueError("test") + except ValueError: + pass + assert not lock.locked() + +def test_acquire_blocking_true() -> None: + lock = Lock() + assert lock.acquire(True) + assert lock.locked() + lock.release() + +def test_lock_constructor_errors() -> None: + lock_type: Any = Lock + make_type: Any = type + with assertRaises(TypeError): + lock_type(1) + with assertRaises(TypeError): + lock_type(foo=1) + with assertRaises(TypeError): + make_type("LockSubclass", (lock_type,), {}) + +def test_lock_acquire_argument_errors() -> None: + lock: Any = Lock() + with assertRaises(TypeError): + lock.acquire(True, False) + with assertRaises(TypeError): + lock.acquire(foo=True) + +def test_lock_acquire_blocking_truthiness() -> None: + lock: Any = Lock() + assert lock.acquire(blocking=True) + assert lock.locked() + assert not lock.acquire(blocking=False) + lock.release() + + assert lock.acquire(None) + assert lock.locked() + assert not lock.acquire(None) + lock.release() + + assert lock.acquire(1) + lock.release() + assert lock.acquire(0) + lock.release() + +def test_lock_acquire_blocking_bool_error() -> None: + lock: Any = Lock() + with assertRaises(RuntimeError, "bad bool"): + lock.acquire(BadBool()) + with assertRaises(RuntimeError, "bad bool"): + lock.acquire(blocking=BadBool()) + +def test_lock_exit_manual_call() -> None: + lock: Any = Lock() + lock.acquire() + assert lock.__exit__(None, None, None) is None + assert not lock.locked() + + lock.acquire() + assert lock.__exit__() is None + assert not lock.locked() + +def test_release_unlocked() -> None: + lock = Lock() + with assertRaises(RuntimeError): + lock.release() + # Also after acquire + release + lock.acquire() + lock.release() + with assertRaises(RuntimeError): + lock.release() diff --git a/mypyc/test-data/run-tuples.test b/mypyc/test-data/run-tuples.test index abbba46386040..58220a1af7efd 100644 --- a/mypyc/test-data/run-tuples.test +++ b/mypyc/test-data/run-tuples.test @@ -432,7 +432,7 @@ def test_user_defined() -> None: [file copysubclass.py] from typing import Any -class subc(tuple[Any]): +class subc(tuple[Any, ...]): pass [file userdefinedtuple.py] diff --git a/mypyc/test/librt_cache.py b/mypyc/test/librt_cache.py index f80059ed64786..bf57b9f54196e 100644 --- a/mypyc/test/librt_cache.py +++ b/mypyc/test/librt_cache.py @@ -162,8 +162,15 @@ def get_librt_path(experimental: bool = True, opt_level: str = "0") -> str: os.makedirs(cache_root, exist_ok=True) + binary_suffix = ".pyd" if sys.platform == "win32" else ".so" + with filelock.FileLock(lock_file, timeout=300): # 5 min timeout - if os.path.exists(marker): + # Reuse the cache only if the build completed *and* the compiled + # binaries still exist. A repo-wide clean of .so/.pyd files can delete + # the cached binaries while leaving the marker behind. + if os.path.exists(marker) and any( + f.endswith(binary_suffix) for f in os.listdir(os.path.join(build_dir, "librt")) + ): return build_dir # Clean up any partial build diff --git a/mypyc/test/test_emit.py b/mypyc/test/test_emit.py index 285488e03c9ae..b2199b2dcb3b2 100644 --- a/mypyc/test/test_emit.py +++ b/mypyc/test/test_emit.py @@ -42,9 +42,23 @@ def test_reg(self) -> None: def test_object_annotation(self) -> None: assert self.emitter.object_annotation("hello, world", "line;") == " /* 'hello, world' */" - assert self.emitter.object_annotation(list(range(30)), "line;") == """\ - /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29] */""" + assert self.emitter.object_annotation(42, "line;") == " /* 42 */" + assert self.emitter.object_annotation((1, "x", None), "line;") == " /* (1, 'x', None) */" + # Annotations containing illegal C comment characters are dropped. + assert self.emitter.object_annotation("a /* b */ c", "line;") == "" + + def test_object_annotation_frozenset_is_deterministic(self) -> None: + assert ( + self.emitter.object_annotation(frozenset({"self", "cls"}), "line;") + == self.emitter.object_annotation(frozenset({"cls", "self"}), "line;") + == " /* frozenset({'cls', 'self'}) */" + ) + assert ( + self.emitter.object_annotation((frozenset({"b", "a"}),), "line;") + == self.emitter.object_annotation((frozenset({"a", "b"}),), "line;") + == " /* (frozenset({'a', 'b'}),) */" + ) + assert self.emitter.object_annotation(frozenset(), "line;") == " /* frozenset() */" def test_emit_line(self) -> None: emitter = self.emitter @@ -55,11 +69,9 @@ def test_emit_line(self) -> None: assert emitter.fragments == ["line;\n", "a {\n", " f();\n", "}\n"] emitter = Emitter(self.context, {}) emitter.emit_line("CPyStatics[0];", ann="hello, world") - emitter.emit_line("CPyStatics[1];", ann=list(range(30))) + emitter.emit_line("CPyStatics[1];", ann=42) assert emitter.fragments[0] == "CPyStatics[0]; /* 'hello, world' */\n" - assert emitter.fragments[1] == """\ -CPyStatics[1]; /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29] */\n""" + assert emitter.fragments[1] == "CPyStatics[1]; /* 42 */\n" def test_emit_undefined_value_for_simple_type(self) -> None: emitter = self.emitter diff --git a/mypyc/test/test_emitclass.py b/mypyc/test/test_emitclass.py index eb04b22495de6..9c3cd02d1100c 100644 --- a/mypyc/test/test_emitclass.py +++ b/mypyc/test/test_emitclass.py @@ -2,8 +2,10 @@ import unittest +from mypyc.analysis.attrdefined import detect_undefined_bitmap from mypyc.codegen.emitclass import getter_name, setter_name, slot_key from mypyc.ir.class_ir import ClassIR +from mypyc.ir.rtypes import int32_rprimitive from mypyc.namegen import NameGenerator @@ -33,3 +35,22 @@ def test_getter_name(self) -> None: generator = NameGenerator([["mod"]]) assert getter_name(cls, "down", generator) == "testing___SomeClass_get_down" + + def test_bitmap_attrs_stable_across_repeat_analysis(self) -> None: + # Regression: detect_undefined_bitmap used to mutate cl.bitmap_attrs + # in place, so under separate=True (one SCC per group) a shared base + # class would accumulate duplicate entries as each subclass's SCC + # walked into it, growing the emitted struct between builds. + base = ClassIR("Base", "mod") + base.attributes = {"i": int32_rprimitive} + sub = ClassIR("Sub", "mod") + sub.attributes = {"j": int32_rprimitive} + base.mro = base.base_mro = [base] + sub.mro = sub.base_mro = [sub, base] + base.children = [sub] + + detect_undefined_bitmap(sub, seen=set()) + for _ in range(10): + detect_undefined_bitmap(sub, seen=set()) + assert base.bitmap_attrs == ["i"] + assert sub.bitmap_attrs == ["i", "j"] diff --git a/mypyc/test/test_exceptions.py b/mypyc/test/test_exceptions.py index e9d4348eb64c5..d842ea1a85eda 100644 --- a/mypyc/test/test_exceptions.py +++ b/mypyc/test/test_exceptions.py @@ -11,7 +11,7 @@ from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase from mypyc.analysis.blockfreq import frequently_executed_blocks -from mypyc.common import TOP_LEVEL_NAME +from mypyc.common import IS_FREE_THREADED, TOP_LEVEL_NAME from mypyc.ir.pprint import format_func from mypyc.test.testutil import ( ICODE_GEN_BUILTINS, @@ -34,6 +34,14 @@ class TestExceptionTransform(MypycDataSuite): def run_case(self, testcase: DataDrivenTestCase) -> None: """Perform a runtime checking transformation test case.""" + + if "_withgil" in testcase.name and IS_FREE_THREADED: + # Test case should only run on a non-free-threaded build. + return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return + with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) try: diff --git a/mypyc/test/test_expand_rtype.py b/mypyc/test/test_expand_rtype.py new file mode 100644 index 0000000000000..90acc61f625d9 --- /dev/null +++ b/mypyc/test/test_expand_rtype.py @@ -0,0 +1,48 @@ +import unittest + +from mypyc.ir.class_ir import ClassIR +from mypyc.ir.rtypes import ( + RInstance, + RTuple, + RTypeVar, + RUnion, + RVec, + int_rprimitive, + str_rprimitive, + void_rtype, +) +from mypyc.rt_expandtype import expand_rtype + + +class TestExpandRType(unittest.TestCase): + def test_trivial(self) -> None: + assert expand_rtype(str_rprimitive, []) == str_rprimitive + assert expand_rtype(str_rprimitive, [int_rprimitive]) == str_rprimitive + assert expand_rtype(void_rtype, []) == void_rtype + + def test_instance(self) -> None: + inst = RInstance(ClassIR("A", "__main__")) + assert expand_rtype(inst, [int_rprimitive]) == inst + + def test_simple_expansion(self) -> None: + assert expand_rtype(RTypeVar(0), [str_rprimitive]) == str_rprimitive + + def test_tuple_expansion(self) -> None: + assert expand_rtype( + RTuple([RTypeVar(0), RTypeVar(1)]), [str_rprimitive, int_rprimitive] + ) == RTuple([str_rprimitive, int_rprimitive]) + + def test_union_expansion(self) -> None: + assert expand_rtype( + RUnion([RTypeVar(0), RTypeVar(1)]), [str_rprimitive, int_rprimitive] + ) == RUnion([str_rprimitive, int_rprimitive]) + + def test_vec_expansion(self) -> None: + assert expand_rtype(RVec(RTypeVar(0)), [str_rprimitive]) == RVec(str_rprimitive) + + def test_nested_expansion(self) -> None: + typ = RUnion([RTuple([RVec(RTypeVar(0)), RTypeVar(1)]), RVec(RVec(RTypeVar(0)))]) + expected = RUnion( + [RTuple([RVec(str_rprimitive), int_rprimitive]), RVec(RVec(str_rprimitive))] + ) + assert expand_rtype(typ, [str_rprimitive, int_rprimitive]) == expected diff --git a/mypyc/test/test_irbuild.py b/mypyc/test/test_irbuild.py index f1f0ec777c3da..6608e6db8e7b4 100644 --- a/mypyc/test/test_irbuild.py +++ b/mypyc/test/test_irbuild.py @@ -36,6 +36,7 @@ "irbuild-statements.test", "irbuild-nested.test", "irbuild-classes.test", + "irbuild-final.test", "irbuild-optional.test", "irbuild-any.test", "irbuild-generics.test", @@ -59,7 +60,9 @@ "irbuild-math.test", "irbuild-weakref.test", "irbuild-librt-strings.test", + "irbuild-librt-random.test", "irbuild-base64.test", + "irbuild-threading.test", "irbuild-time.test", "irbuild-match.test", ] @@ -85,6 +88,9 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: if "_withgil" in testcase.name and IS_FREE_THREADED: # Test case should only run on a non-free-threaded build. return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) expected_output = replace_word_size(expected_output) diff --git a/mypyc/test/test_literals.py b/mypyc/test/test_literals.py index a8c17d10d30d0..f1dc7f2434414 100644 --- a/mypyc/test/test_literals.py +++ b/mypyc/test/test_literals.py @@ -10,6 +10,7 @@ _encode_int_values, _encode_str_values, format_str_literal, + literal_sort_key, ) @@ -88,3 +89,50 @@ def test_tuple_literal(self) -> None: "7", # Second tuple (length=4) "0", # Third tuple (length=0) ] + + def test_frozenset_literal_index_is_deterministic(self) -> None: + # Index assignment for members must not depend on frozenset iteration + # order (which is hash-seed dependent), so that generated code is + # reproducible. + lit1 = Literals() + lit1.record_literal(frozenset({"self", "cls"})) + lit2 = Literals() + lit2.record_literal(frozenset({"cls", "self"})) + for s in ("self", "cls"): + assert lit1.literal_index(s) == lit2.literal_index(s) + # Members are recorded in sorted order. + assert lit1.literal_index("cls") == 3 + assert lit1.literal_index("self") == 4 + + def test_frozenset_encoding_is_deterministic(self) -> None: + lit1 = Literals() + lit1.record_literal(frozenset({"self", "cls"})) + lit2 = Literals() + lit2.record_literal(frozenset({"cls", "self"})) + assert lit1.encoded_frozenset_values() == lit2.encoded_frozenset_values() + + def test_literal_sort_key_is_total_over_types(self) -> None: + # Heterogeneous, individually unorderable items must still be sorted. + values = ["x", b"y", 1, None, (1, 2), frozenset({1, 2})] + values_reversed = list(reversed(values)) + assert sorted(values, key=literal_sort_key) == sorted( + values_reversed, key=literal_sort_key + ) + + def test_literal_sort_key_with_frozenset(self) -> None: + assert literal_sort_key(frozenset({"a", "b"})) == literal_sort_key(frozenset({"b", "a"})) + assert literal_sort_key((frozenset({"a", "b"}),)) == literal_sort_key( + (frozenset({"b", "a"}),) + ) + assert literal_sort_key(frozenset({"a", frozenset({"b", "c"})})) == literal_sort_key( + frozenset({frozenset({"c", "b"}), "a"}) + ) + + def test_nested_frozenset_literal_index_is_deterministic(self) -> None: + lit1 = Literals() + lit1.record_literal(frozenset({frozenset({"a", "b"}), frozenset({"c", "d"})})) + lit2 = Literals() + lit2.record_literal(frozenset({frozenset({"d", "c"}), frozenset({"b", "a"})})) + for s in ("a", "b", "c", "d"): + assert lit1.literal_index(s) == lit2.literal_index(s) + assert lit1.encoded_frozenset_values() == lit2.encoded_frozenset_values() diff --git a/mypyc/test/test_lowering.py b/mypyc/test/test_lowering.py index e27b4e77eea8a..75351cd50e4a7 100644 --- a/mypyc/test/test_lowering.py +++ b/mypyc/test/test_lowering.py @@ -7,7 +7,7 @@ from mypy.errors import CompileError from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase -from mypyc.common import TOP_LEVEL_NAME +from mypyc.common import IS_FREE_THREADED, TOP_LEVEL_NAME from mypyc.ir.pprint import format_func from mypyc.options import CompilerOptions from mypyc.test.testutil import ( @@ -28,7 +28,7 @@ class TestLowering(MypycDataSuite): - files = ["lowering-int.test", "lowering-list.test"] + files = ["lowering-int.test", "lowering-list.test", "lowering-vec.test"] base_path = test_temp_dir def run_case(self, testcase: DataDrivenTestCase) -> None: @@ -36,6 +36,12 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: if options is None: # Skipped test case return + if "_withgil" in testcase.name and IS_FREE_THREADED: + # Test case should only run on a non-free-threaded build. + return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) expected_output = replace_word_size(expected_output) diff --git a/mypyc/test/test_misc.py b/mypyc/test/test_misc.py index 4b0bbe5988afb..816875fcc23db 100644 --- a/mypyc/test/test_misc.py +++ b/mypyc/test/test_misc.py @@ -1,7 +1,10 @@ from __future__ import annotations +import os +import tempfile import unittest +from mypyc.build import get_header_deps, resolve_cfile_deps from mypyc.ir.ops import BasicBlock from mypyc.ir.pprint import format_blocks, generate_names_for_ir from mypyc.irbuild.ll_builder import LowLevelIRBuilder @@ -20,3 +23,130 @@ def test_debug_op(self) -> None: names = generate_names_for_ir([], [block]) code = format_blocks([block], names, {}) assert code[:-1] == ["L0:", " r0 = 'foo'", " CPyDebug_PrintObject(r0)"] + + +class TestHeaderDeps(unittest.TestCase): + """ + Tests for the header-dependency tracking used to build `Extension.depends`, which drives + setuptools' `newer_group` decision about whether to recompile a .o file on incremental builds. + """ + + def test_get_header_deps_quoted_includes(self) -> None: + # Quoted includes: the historical form. Used by the .c file to reach its own __native_.h / + # __native_internal_.h. The `False` in each tuple marks the include as non-angled, which + # `resolve_cfile_deps` uses to search the includer's directory. + cfile = '#include "__native_caller.h"\n#include "__native_internal_caller.h"\n' + assert get_header_deps([("caller.c", cfile)]) == [ + (False, "__native_caller.h"), + (False, "__native_internal_caller.h"), + ] + + def test_get_header_deps_angle_bracket_includes(self) -> None: + # Angle-bracket includes are also matched, and reported with is_angled=True so that the resolver + # skips the includer's dir for them (matching the C preprocessor). The cross-group export header + # is reached via `#include ` in __native_internal_.h. Before + # this was matched the dep was missed entirely and the consumer's .o was never invalidated when + # the other group's struct layout shifted. + cfile = "#include \n#include \n" + assert get_header_deps([("caller.c", cfile)]) == [ + (True, "Python.h"), + (True, "lib/__native_functions.h"), + ] + + def test_get_header_deps_mixed_and_whitespace(self) -> None: + # The preprocessor tolerates whitespace and the leading-hash form. `get_header_deps` returns sorted + # tuples — non-angled (False) sorts before angled (True), then alphabetical within each kind. + cfile = '# include "a.h"\n# include \n#include\t"c.h"\n' + assert get_header_deps([("x.c", cfile)]) == [(False, "a.h"), (False, "c.h"), (True, "b.h")] + + def test_resolve_walks_transitively_through_headers(self) -> None: + # Reproduces the bug scenario: caller's .c only directly includes caller's own headers, but + # caller's __native_internal_caller.h includes the cross-group export header. The resolver + # must follow that chain so setuptools sees the cross-group header as a dep. + with tempfile.TemporaryDirectory() as tmp: + build_dir = tmp + os.makedirs(os.path.join(build_dir, "lib")) + os.makedirs(os.path.join(build_dir, "other_group")) + + # caller.c's directly-included headers, both live alongside + # caller.c under build/ (resolved via target_dir). + internal_h = os.path.join(build_dir, "__native_internal_caller.h") + caller_h = os.path.join(build_dir, "__native_caller.h") + cross_group_h = os.path.join(build_dir, "lib", "__native_functions.h") + unrelated_h = os.path.join(build_dir, "other_group", "__native_other.h") + + with open(caller_h, "w") as f: + # Headers outside build/ (CPython's , lib-rt's ) don't resolve under + # target_dir, so they get dropped during resolution and aren't recursed into. + f.write("#include \n#include \n") + with open(internal_h, "w") as f: + # This header includes a header in another group via angle brackets. Pre-fix, this dep + # was invisible to setuptools. + f.write( + "#include \n" + '#include "__native_caller.h"\n' + "#include \n" + ) + with open(cross_group_h, "w") as f: + f.write("struct export_table_lib___functions { int x; };\n") + with open(unrelated_h, "w") as f: + # Sibling group not reached from caller's chain => must NOT appear in the resolved set. + f.write("struct unrelated { int x; };\n") + + # caller.c is in build_dir, so its includer-dir is build_dir. Both directly-included headers + # are quoted (`False`); the cross-group header that __native_internal_caller.h reaches via + # `` is found by the recursive walk re-reading the on-disk header. + deps = resolve_cfile_deps( + cfile_dir=build_dir, + direct_includes=[ + (False, "__native_caller.h"), + (False, "__native_internal_caller.h"), + ], + target_dir=build_dir, + ) + + assert deps == {caller_h, internal_h, cross_group_h}, ( + f"expected the cross-group header to be reached transitively; " + f"got {sorted(deps)!r}" + ) + + def test_resolve_drops_unresolvable_includes(self) -> None: + # ``, ``, etc. don't live under target_dir, so they're dropped from depends. They + # never change between builds, so this is the right behavior. Crucially, it stops setuptools' + # `missing="newer"` from treating them as always-newer and force-rebuilding every translation unit. + with tempfile.TemporaryDirectory() as tmp: + cfile_dir = tmp + deps = resolve_cfile_deps( + cfile_dir=cfile_dir, + direct_includes=[(True, "Python.h"), (True, "CPy.h"), (False, "init.c")], + target_dir=cfile_dir, + ) + assert deps == set() + + def test_resolve_search_order_matches_preprocessor(self) -> None: + # When the same header name exists both next to the includer and under target_dir, the C preprocessor + # picks the includer-dir copy for `#include "shared.h"` and the target_dir copy for `#include `. + # The resolver must record the same path the compiler will actually consume, otherwise mtimes of the + # wrong file drive incremental rebuild decisions. + with tempfile.TemporaryDirectory() as tmp: + includer = os.path.join(tmp, "groupA") + target = os.path.join(tmp, "build") + os.makedirs(includer) + os.makedirs(target) + + local_h = os.path.join(includer, "shared.h") + global_h = os.path.join(target, "shared.h") + with open(local_h, "w") as f: + f.write("/* local */\n") + with open(global_h, "w") as f: + f.write("/* global */\n") + + # Quoted form: resolves to the includer-dir copy. + assert resolve_cfile_deps( + cfile_dir=includer, direct_includes=[(False, "shared.h")], target_dir=target + ) == {local_h} + + # Angled form: skips the includer-dir copy, resolves under -I. + assert resolve_cfile_deps( + cfile_dir=includer, direct_includes=[(True, "shared.h")], target_dir=target + ) == {global_h} diff --git a/mypyc/test/test_optimizations.py b/mypyc/test/test_optimizations.py index 6ca53b134ba91..c6a40d08e2e67 100644 --- a/mypyc/test/test_optimizations.py +++ b/mypyc/test/test_optimizations.py @@ -7,7 +7,7 @@ from mypy.errors import CompileError from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase -from mypyc.common import TOP_LEVEL_NAME +from mypyc.common import IS_FREE_THREADED, TOP_LEVEL_NAME from mypyc.ir.func_ir import FuncIR from mypyc.ir.pprint import format_func from mypyc.options import CompilerOptions @@ -33,6 +33,12 @@ class OptimizationSuite(MypycDataSuite): base_path = test_temp_dir def run_case(self, testcase: DataDrivenTestCase) -> None: + if "_withgil" in testcase.name and IS_FREE_THREADED: + # Test case should only run on a non-free-threaded build. + return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) try: diff --git a/mypyc/test/test_refcount.py b/mypyc/test/test_refcount.py index 7efa861e784fb..896ba14e2e68b 100644 --- a/mypyc/test/test_refcount.py +++ b/mypyc/test/test_refcount.py @@ -11,7 +11,7 @@ from mypy.errors import CompileError from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase -from mypyc.common import TOP_LEVEL_NAME +from mypyc.common import IS_FREE_THREADED, TOP_LEVEL_NAME from mypyc.ir.pprint import format_func from mypyc.test.testutil import ( ICODE_GEN_BUILTINS, @@ -40,6 +40,12 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: if options is None: # Skipped test case return + if "_withgil" in testcase.name and IS_FREE_THREADED: + # Test case should only run on a non-free-threaded build. + return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) expected_output = replace_word_size(expected_output) diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 8fb861f5c2aae..868f83654b47c 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -81,6 +81,8 @@ "run-librt-strings.test", "run-base64.test", "run-librt-time.test", + "run-librt-random.test", + "run-threading.test", "run-match.test", "run-vecs-i64-interp.test", "run-vecs-misc-interp.test", @@ -233,6 +235,10 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> # Avoid checking modules/packages named 'unchecked', to provide a way # to test interacting with code we don't have types for. options.per_module_options["unchecked.*"] = {"follow_imports": "error"} + # Avoid checking modules/packages named 'skipped', to provide a way + # to test interacting with code ignored by follow_imports=skip. + options.per_module_options["skipped"] = {"follow_imports": "skip"} + options.per_module_options["skipped.*"] = {"follow_imports": "skip"} source = build.BuildSource("native.py", "native", None) sources = [source] diff --git a/mypyc/test/testutil.py b/mypyc/test/testutil.py index 0a558d0d0b8ec..9d59993c03402 100644 --- a/mypyc/test/testutil.py +++ b/mypyc/test/testutil.py @@ -232,8 +232,14 @@ def show_c(cfiles: list[list[tuple[str, str]]]) -> None: def fudge_dir_mtimes(dir: str, delta: int) -> None: + # Skip linker outputs. Pushing them back combines with write_file's + # +1 sec bump on .c files to make .c always newer than .so, forcing + # an unconditional rebuild that would mask Extension.depends bugs. + # See setuptools/_distutils/command/build_ext.py:`build_extension`. for dirpath, _, filenames in os.walk(dir): for name in filenames: + if name.endswith((".so", ".pyd", ".o", ".obj")): + continue path = os.path.join(dirpath, name) new_mtime = os.stat(path).st_mtime + delta os.utime(path, times=(new_mtime, new_mtime)) diff --git a/mypyc/transform/spill.py b/mypyc/transform/spill.py index d92dd661e7eb4..c2d4d4311375d 100644 --- a/mypyc/transform/spill.py +++ b/mypyc/transform/spill.py @@ -2,6 +2,9 @@ from __future__ import annotations +from collections.abc import Collection +from typing import cast + from mypyc.analysis.dataflow import AnalysisResult, analyze_live_regs, get_cfg from mypyc.common import TEMP_ATTR_NAME from mypyc.ir.class_ir import ClassIR @@ -13,6 +16,7 @@ GetAttr, IncRef, LoadErrorValue, + Op, Register, SetAttr, Value, @@ -31,6 +35,19 @@ def insert_spills(ir: FuncIR, env: ClassIR) -> None: ir.blocks = spill_regs(ir.blocks, env, entry_live, live, ir.arg_regs[0]) +def sort_values(values: Collection[Op], blocks: list[BasicBlock]) -> list[Op]: + if len(values) > 1: + order = {} + i = 0 + for block in blocks: + for op in block.ops: + order[op] = i + i += 1 + return sorted(values, key=lambda v: order[v]) + else: + return list(values) + + def spill_regs( blocks: list[BasicBlock], env: ClassIR, @@ -48,7 +65,9 @@ def spill_regs( env_reg = self_reg spill_locs = {} - for i, val in enumerate(to_spill): + # Sort values to make the order deterministic. All the spilled values are + # known to be Op instances, so the cast is safe. + for i, val in enumerate(sort_values(cast(set[Op], to_spill), blocks)): name = f"{TEMP_ATTR_NAME}2_{i}" env.attributes[name] = val.type if val.type.error_overlap: diff --git a/pyproject.toml b/pyproject.toml index 9313335b0d969..aa9432c06e48b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,12 @@ requires = [ "mypy_extensions>=1.0.0", "pathspec>=1.0.0", "tomli>=1.1.0; python_version<'3.11'", - "librt>=0.10.0; platform_python_implementation != 'PyPy'", + "librt>=0.13.0; platform_python_implementation != 'PyPy'", # the following is from build-requirements.txt "types-psutil", "types-setuptools", # required to work around a mypyc import bug - "ast-serialize>=0.3.0,<1.0.0", + "ast-serialize>=0.6.0,<1.0.0", ] build-backend = "setuptools.build_meta" @@ -46,6 +46,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: Software Development", "Typing :: Typed", ] @@ -57,8 +58,8 @@ dependencies = [ "mypy_extensions>=1.0.0", "pathspec>=1.0.0", "tomli>=1.1.0; python_version<'3.11'", - "librt>=0.10.0; platform_python_implementation != 'PyPy'", - "ast-serialize>=0.3.0,<1.0.0", + "librt>=0.13.0; platform_python_implementation != 'PyPy'", + "ast-serialize>=0.6.0,<1.0.0", ] dynamic = ["version"] diff --git a/setup.py b/setup.py index d36a6bfa2c2dc..1879f6892ba8f 100644 --- a/setup.py +++ b/setup.py @@ -153,6 +153,7 @@ def run(self) -> None: debug_level = os.getenv("MYPYC_DEBUG_LEVEL", "1") force_multifile = os.getenv("MYPYC_MULTI_FILE", "") == "1" log_trace = bool(int(os.getenv("MYPYC_LOG_TRACE", "0"))) + separate = os.getenv("MYPYC_SEPARATE", "") == "1" ext_modules = mypycify( mypyc_targets + ["--config-file=mypy_bootstrap.ini"], opt_level=opt_level, @@ -161,6 +162,7 @@ def run(self) -> None: # our Appveyor builds run out of memory sometimes. multi_file=sys.platform == "win32" or force_multifile, log_trace=log_trace, + separate=separate, # Mypy itself is allowed to use native_internal extension. depends_on_librt_internal=True, ) diff --git a/test-data/unit/check-annotated.test b/test-data/unit/check-annotated.test index d4de3f7b5043f..03ae8906f1d32 100644 --- a/test-data/unit/check-annotated.test +++ b/test-data/unit/check-annotated.test @@ -41,6 +41,11 @@ x: Annotated[int] # E: Annotated[...] must have exactly one type argument and a reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] +[case testAnnotatedBadEmptyTupleIndexInTypeApplication] +from typing_extensions import Annotated +list[Annotated[()]] # E: Annotated[...] must have exactly one type argument and at least one annotation +[builtins fixtures/list.pyi] + [case testAnnotatedNested0] from typing_extensions import Annotated x: Annotated[Annotated[int, ...], ...] diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index e887b4c575528..cd40f2cf29a65 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -839,6 +839,34 @@ def bar() -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] +[case testUntypedAsyncFunctionAndMethodReturnCoroutine] +# flags: --show-error-codes +async def foo(): + pass + +class C: + async def method(self): + pass + +def f(c: C) -> None: + a = foo() + b = c.method() + reveal_type(foo) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, Any]" + reveal_type(a) # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \ + # N: Are you missing an await? \ + # N: Revealed type is "typing.Coroutine[Any, Any, Any]" + foo() # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \ + # N: Are you missing an await? + reveal_type(c.method) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, Any]" + reveal_type(b) # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \ + # N: Are you missing an await? \ + # N: Revealed type is "typing.Coroutine[Any, Any, Any]" + c.method() # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \ + # N: Are you missing an await? + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + [case testAsyncForOutsideCoroutine] async def g(): yield 0 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 5a66eff2bd3b7..2cd43c74ebb5b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3889,6 +3889,24 @@ def process(cls: Type[U]): [builtins fixtures/classmethod.pyi] [out] +[case testTypeUsingTypeCConstructorReturnFromTypeVarUnionBound] +from typing import Optional, Type, TypeVar, Union + +class A: + def __init__(self, value: str = "") -> None: pass +class B: + def __init__(self, value: str = "") -> None: pass + +T = TypeVar("T", bound=Union[A, B]) + +def make(ftype: Type[T], value: Optional[str]) -> T: + if value is None: + return ftype() + return ftype(value) + +reveal_type(make(A, "a")) # N: Revealed type is "__main__.A" +reveal_type(make(B, None)) # N: Revealed type is "__main__.B" + [case testTypeUsingTypeCErrorUnsupportedType] from typing import Type, Tuple def foo(arg: Type[Tuple[int]]): @@ -7317,18 +7335,25 @@ reveal_type(B()) # N: Revealed type is "__main__.B" [case testNewReturnType2] from typing import Any -# make sure that __new__ method that return Any are ignored when +# make sure that __new__ method that return implicit Any are ignored when # determining the return type class A: def __new__(cls): pass class B: - def __new__(cls) -> Any: + def __new__(cls, x: int = 0) -> Any: + pass + +class C: + def __new__(cls, x: int = 0): pass reveal_type(A()) # N: Revealed type is "__main__.A" -reveal_type(B()) # N: Revealed type is "__main__.B" +reveal_type(B()) # N: Revealed type is "Any" +reveal_type(B(1)) # N: Revealed type is "Any" +reveal_type(C()) # N: Revealed type is "__main__.C" +reveal_type(C(1)) # N: Revealed type is "__main__.C" [case testNewReturnType3] @@ -7338,7 +7363,7 @@ class A: def __new__(cls) -> int: # E: Incompatible return type for "__new__" (returns "int", but must return a subtype of "A") pass -reveal_type(A()) # N: Revealed type is "__main__.A" +reveal_type(A()) # N: Revealed type is "builtins.int" [case testNewReturnType4] from typing import TypeVar, Type @@ -7448,6 +7473,287 @@ class MyMetaClass(type): class MyClass(metaclass=MyMetaClass): pass +[case testNewReturnType13] +from typing import Protocol + +class Foo(Protocol): + def foo(self) -> str: ... + +class A: + def __new__(cls) -> Foo: ... # E: Incompatible return type for "__new__" (returns "Foo", but must return a subtype of "A") + +reveal_type(A()) # N: Revealed type is "__main__.Foo" +reveal_type(A().foo()) # N: Revealed type is "builtins.str" + +[case testNewReturnType14] +from __future__ import annotations + +class A: + def __new__(cls) -> int: raise # E: Incompatible return type for "__new__" (returns "int", but must return a subtype of "A") + +class B(A): + @classmethod + def foo(cls) -> int: raise + +reveal_type(B.foo()) # N: Revealed type is "builtins.int" +[builtins fixtures/classmethod.pyi] + +[case testNewReturnType15] +from typing import Generic, Type, TypeVar + +T = TypeVar("T") + +class A(Generic[T]): + def __new__(cls) -> B[int]: ... + @classmethod + def foo(cls: Type[A[T]]) -> T: ... + +class B(A[T]): ... + +# The Never without error is not ideal, but matches the behavior without custom __new__(). +reveal_type(B.foo()) # N: Revealed type is "Never" +reveal_type(B[str].foo()) # N: Revealed type is "builtins.str" + +class C(A[str]): ... + +reveal_type(C.foo()) # N: Revealed type is "builtins.str" +[builtins fixtures/classmethod.pyi] + +[case testNewReturnType16] +from typing import Generic, TypeVar + +T = TypeVar("T") +class A(Generic[T]): + def __new__(cls, *args, **kwargs) -> T: # E: "__new__" must return a class instance (got "T") + ... + +class Model: + pass + +reveal_type(A[Model]()) # N: Revealed type is "__main__.Model" + +class B(A[Model]): + pass + +reveal_type(B()) # N: Revealed type is "__main__.Model" +[builtins fixtures/dict.pyi] + +[case testNewReturnType17] +class C: + def __new__(self) -> D: + return D() + +class D(C): + x: int + +C.x # E: "type[C]" has no attribute "x" + +[case testNewReturnType18] +class A: + def __new__(cls) -> A: + return A() + +class B(A): + def __new__(cls) -> A: # E: Incompatible return type for "__new__" (returns "A", but must return a subtype of "B") + return super().__new__(cls) + +class C(B): ... + +# Always respect explicit return type after giving an error. +reveal_type(B()) # N: Revealed type is "__main__.A" +reveal_type(C()) # N: Revealed type is "__main__.A" + +# Ignore "implicit" return type to preserve backwards compatibility. +class D(A): ... +reveal_type(D()) # N: Revealed type is "__main__.D" + +[case testNewReturnType19] +from typing import TypeVar + +T = TypeVar("T") + +def f(tp: type[T]) -> T: ... + +class C: + def __new__(cls) -> int: ... # type: ignore[misc] + +reveal_type(f(C)) # N: Revealed type is "__main__.C" + +[case testNewReturnTypeCallableSubtyping] +from typing import Callable, Protocol + +class Foo: + def foo(self) -> None: ... + +class A: + def __new__(cls) -> Foo: ... # type: ignore + +ta: type[A] +tf: type[Foo] + +class PA(Protocol): + def __call__(self) -> A: ... + +class PFoo(Protocol): + def __call__(self) -> Foo: ... + +def f(x: Callable[[], A]) -> None: ... +def g(x: Callable[[], Foo]) -> None: ... +def fp(x: PA) -> None: ... +def gp(x: PFoo) -> None: ... +def h(x: type[A]) -> None: ... +def i(x: type[Foo]) -> None: ... + +f(A) # E: Argument 1 to "f" has incompatible type "type[A]"; expected "Callable[[], A]" +g(A) +fp(A) # E: Argument 1 to "fp" has incompatible type "type[A]"; expected "PA" \ + # N: "A" has constructor incompatible with "__call__" of "PA" \ + # N: Following member(s) of "A" have conflicts: \ + # N: Expected: \ + # N: def __call__() -> A \ + # N: Got: \ + # N: def A() -> Foo \ + # N: "PA.__call__" has type "Callable[[], A]" +gp(A) +h(A) +i(A) # E: Argument 1 to "i" has incompatible type "type[A]"; expected "type[Foo]" + +f(Foo) # E: Argument 1 to "f" has incompatible type "type[Foo]"; expected "Callable[[], A]" +g(Foo) +fp(Foo) # E: Argument 1 to "fp" has incompatible type "type[Foo]"; expected "PA" \ + # N: "Foo" has constructor incompatible with "__call__" of "PA" \ + # N: Following member(s) of "Foo" have conflicts: \ + # N: Expected: \ + # N: def __call__() -> A \ + # N: Got: \ + # N: def Foo() -> Foo \ + # N: "PA.__call__" has type "Callable[[], A]" +gp(Foo) +h(Foo) # E: Argument 1 to "h" has incompatible type "type[Foo]"; expected "type[A]" +i(Foo) + +f(ta) # E: Argument 1 to "f" has incompatible type "type[A]"; expected "Callable[[], A]" +g(ta) +fp(ta) # E: Argument 1 to "fp" has incompatible type "type[A]"; expected "PA" \ + # N: "A" has constructor incompatible with "__call__" of "PA" \ + # N: Following member(s) of "A" have conflicts: \ + # N: Expected: \ + # N: def __call__() -> A \ + # N: Got: \ + # N: def A() -> Foo \ + # N: "PA.__call__" has type "Callable[[], A]" +gp(ta) +h(ta) +i(ta) # E: Argument 1 to "i" has incompatible type "type[A]"; expected "type[Foo]" + +f(tf) # E: Argument 1 to "f" has incompatible type "type[Foo]"; expected "Callable[[], A]" +g(tf) +fp(tf) # E: Argument 1 to "fp" has incompatible type "type[Foo]"; expected "PA" \ + # N: "Foo" has constructor incompatible with "__call__" of "PA" \ + # N: Following member(s) of "Foo" have conflicts: \ + # N: Expected: \ + # N: def __call__() -> A \ + # N: Got: \ + # N: def Foo() -> Foo \ + # N: "PA.__call__" has type "Callable[[], A]" +gp(tf) +h(tf) # E: Argument 1 to "h" has incompatible type "type[Foo]"; expected "type[A]" +i(tf) + +[case testNewReturnTypeCallableJoins] +from typing import TypeVar + +class Base: ... +class Foo(Base): ... +class Bar(Base): ... + +class Base2: ... + +class A(Base2): + def __new__(cls) -> Foo: ... # type: ignore + +class B(Base2): + def __new__(cls) -> Bar: ... # type: ignore + +T = TypeVar("T") +def join(x: T, y: T) -> T: ... + +reveal_type(join(A, Foo)) # N: Revealed type is "def () -> __main__.Foo" +reveal_type(join(Foo, A)) # N: Revealed type is "def () -> __main__.Foo" +reveal_type(join(A, B)) # N: Revealed type is "def () -> __main__.Base" +reveal_type(join(B, A)) # N: Revealed type is "def () -> __main__.Base" + +# This tests the type repr in error messages. +j = join(A, B) +x: int = j # E: Incompatible types in assignment (expression has type "type[Base2]", variable has type "int") + +[case testNewReturnTypeCallableInference] +from typing import Callable, TypeVar + +class A: + def __new__(cls) -> int: ... # type: ignore + +T = TypeVar("T") +def foo(f: Callable[[], T]) -> T: ... +def bar(t: type[T]) -> T: ... + +ta: type[A] +reveal_type(foo(A)) # N: Revealed type is "builtins.int" +reveal_type(foo(ta)) # N: Revealed type is "builtins.int" +reveal_type(bar(A)) # N: Revealed type is "__main__.A" +reveal_type(bar(ta)) # N: Revealed type is "__main__.A" + +[case testNewReturnTypeCallableNarrowing] +from typing import Callable, TypeVar + +class A: ... + +class B(A): + more = 1 + def __new__(cls) -> A: ... # type: ignore + +x: A +if isinstance(x, B): + reveal_type(x) # N: Revealed type is "__main__.B" + +tx: type[A] +if issubclass(tx, B): + reveal_type(tx) # N: Revealed type is "type[__main__.B]" +[builtins fixtures/isinstancelist.pyi] + +[case testNewReturnTypeOverloadNarrowing] +from typing import TypeVar, Generic, Union, overload + +T = TypeVar("T", int, str) + +class Series(Generic[T]): + @overload + def __new__(cls, dtype: int) -> IntSeries: ... + @overload + def __new__(cls, dtype: type[T]) -> Series[T]: ... + @overload + def __new__(cls, dtype=...) -> Series: ... + def __new__(cls, dtype=...) -> Series: + ... + +class IntSeries(Series[int]): ... + +class Index: + @overload + def __new__(cls, dtype: int) -> IntIndex: ... + @overload + def __new__(cls, dtype=...) -> Index: ... + def __new__(cls, dtype=...) -> Index: + ... + +class IntIndex(Index): ... + +def foo(x: Union[Series, int, Index]): + if isinstance(x, (Series, Index)): + reveal_type(x) # N: Revealed type is "__main__.Series[Any] | __main__.Index" + else: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/isinstancelist.pyi] [case testMetaclassPlaceholderNode] from sympy.assumptions import ManagedProperties @@ -9412,3 +9718,12 @@ from typ import NT def f() -> NT: return NT(x='') [builtins fixtures/tuple.pyi] + +[case testClassNestedInFunctionNotLeaking] +from a import X # E: Module "a" has no attribute "X" +reveal_type(X) # N: Revealed type is "Any" +[file a.py] +def f() -> None: + class X: + ... + undefined # E: Name "undefined" is not defined diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 762585806b85d..5ac0318421167 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1252,6 +1252,26 @@ class Person: [builtins fixtures/dataclasses.pyi] +[case testDataclassesFloatSerializationIncremental] +import m +[file m.py] +from lib import MyDataClass +[file m.py.2] +from lib import MyDataClass + +reveal_type(MyDataClass.MY_CONSTANT) +[file lib.py] +from dataclasses import dataclass +from typing import Final + +@dataclass(kw_only=True, repr=False) +class MyDataClass: + MY_CONSTANT: Final = 1.234 +[builtins fixtures/dataclasses.pyi] +[out] +[out2] +tmp/m.py:3: note: Revealed type is "Literal[1.234]?" + [case testDataclassesDefaultsMroOtherFile] import a @@ -2589,6 +2609,27 @@ class Y(X): [builtins fixtures/tuple.pyi] +[case testDunderReplaceDoesNotBlockPlainIssubclassNarrowing] +# https://github.com/python/mypy/issues/21635 +# flags: --python-version 3.13 +from dataclasses import dataclass + +@dataclass +class A: ... +@dataclass +class M: ... +@dataclass +class B(A): ... +@dataclass +class C(M, A): ... + +cls: type[A] = C +if issubclass(cls, M): + reveal_type(cls) # N: Revealed type is "type[__main__.]" + n: int = 'foo' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/isinstancelist.pyi] + + [case testFrozenWithFinal] from dataclasses import dataclass from typing import Final diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 55a20e2b3fa2c..20cf3b5aebfbc 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -604,8 +604,9 @@ V = Enum('U', **{'a': 1}) # E: Unexpected arguments to Enum() W = Enum('W', 'a b') W.c # E: "type[W]" has no attribute "c" X = Enum('Something', 'a b') # E: String argument 1 "Something" to enum.Enum(...) does not match variable name "X" -reveal_type(X.a) # N: Revealed type is "Literal[__main__.Something@23.a]?" -X.asdf # E: "type[Something@23]" has no attribute "asdf" +reveal_type(X.a) # N: Revealed type is "Literal[__main__.X.a]?" +X.asdf # E: "type[X]" has no attribute "asdf" + [builtins fixtures/tuple.pyi] [typing fixtures/typing-medium.pyi] @@ -633,7 +634,7 @@ a = A() reveal_type(a.x) [builtins fixtures/enum.pyi] [out] -main:7: note: Revealed type is "__main__.A.E@4" +main:7: note: Revealed type is "__main__.E@4" [case testFunctionalEnumInClassBody] from enum import Enum diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 85a2264c2088b..5296d813334e4 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -331,6 +331,17 @@ def h(x: int, y: int, z: int) -> None: pass h(y=1, z=1) # E: Missing positional argument "x" in call to "h" [call-arg] h(y=1) # E: Missing positional arguments "x", "z" in call to "h" [call-arg] +[case testTooManyPositionalArgumentsErrorCode] +def f(a: int, *, b: int) -> None: pass +f(1, 2) # E: Too many positional arguments for "f" [call-arg] +f(1, 2) # type: ignore[call-arg] +f(1, 2) # type: ignore[misc] + +[case testTooManyPositionalArgumentsCoveredByMiscUnused] +# flags: --warn-unused-ignores +def f(a: int, *, b: int) -> None: pass +f(1, 2) # type: ignore[misc] # E: Unused "type: ignore" comment, use narrower [call-arg] instead of [misc] code [unused-ignore] + [case testErrorCodeArgType] def f(x: int) -> None: pass f('') # E: Argument 1 to "f" has incompatible type "str"; expected "int" [arg-type] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index a281218af58e0..acdee2a10430a 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -59,7 +59,7 @@ async def f(): # E: Function is missing a return type annotation \ # N: Use "-> None" if function does not return a value pass [builtins fixtures/async_await.pyi] -[typing fixtures/typing-medium.pyi] +[typing fixtures/typing-async.pyi] [case testAsyncUnannotatedArgument] # flags: --disallow-untyped-defs @@ -2717,3 +2717,34 @@ if ( or z is None ): pass + +[case testRecursiveTypeVarDefaultMutualDisallow] +# flags: --disallow-any-generic +from typing import TypeVar, Generic + +class C(Generic["T"]): + pass + +class D(Generic["S"]): + pass + +T = TypeVar("T", default=D) # E: Missing type arguments for generic type "D" \ + # N: Cyclic type variable defaults are not supported +S = TypeVar("S", default=C) # E: Missing type arguments for generic type "C" \ + # N: Cyclic type variable defaults are not supported + +c: C +d: D +reveal_type(c) # N: Revealed type is "__main__.C[__main__.D[Any]]" +reveal_type(d) # N: Revealed type is "__main__.D[__main__.C[Any]]" + +[case testRecursiveTypeVarDefaultOnlyAliasDisallow] +# flags: --disallow-any-generic +from typing import TypeVar + +T = TypeVar("T", default="A") # E: Missing type arguments for generic type "A" \ + # N: Cyclic type variable defaults are not supported +A = list[T] +a: A +reveal_type(a) # N: Revealed type is "builtins.list[builtins.list[Any]]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index bd2bf26613940..893eefb36f874 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3814,9 +3814,9 @@ f(1, b'x', 1) main:3: error: Missing positional argument "y" in call to "f" [case testMissingPositionalArgShiftDetectedFirst] -def f(x: int, y: str, z: bytes) -> None: ... +def f(x: int, y: str, z: bytes, last: float) -> None: ... -f("hello", b'x') +f("hello", b'x', 1.5) [builtins fixtures/primitives.pyi] [out] main:3: error: Missing positional argument "x" in call to "f" @@ -3891,3 +3891,46 @@ f("hello", b'x') main:3: error: Missing positional argument "z" in call to "f" main:3: error: Argument 1 to "f" has incompatible type "str"; expected "int" main:3: error: Argument 2 to "f" has incompatible type "bytes"; expected "str" + +[case testMissingPositionalArgNamesHigherN] +# See https://github.com/python/mypy/issues/21427 +def convert2(first: int, second: str) -> None: ... + +# Possibly omitted arg, but we still issue two errors because there is only one argument +convert2("hello") # E: Missing positional argument "second" in call to "convert2" \ + # E: Argument 1 to "convert2" has incompatible type "str"; expected "int" + +# Other cases +convert2() # E: Missing positional arguments "first", "second" in call to "convert2" + +convert2("hello", 1) # E: Argument 1 to "convert2" has incompatible type "str"; expected "int" \ + # E: Argument 2 to "convert2" has incompatible type "int"; expected "str" + +def convert3(first: int, second: str, third: float) -> None: ... + +# Possibly omitted arg, but we now only issue one error +convert3("hello", 3.15) # E: Missing positional argument "first" in call to "convert3" + +# Other cases +convert3("hello") # E: Missing positional arguments "second", "third" in call to "convert3" \ + # E: Argument 1 to "convert3" has incompatible type "str"; expected "int" + +convert3(3.15, "hello") # E: Missing positional argument "third" in call to "convert3" \ + # E: Argument 1 to "convert3" has incompatible type "float"; expected "int" + +def convert4(first: int, second: str, third: float, fourth: bytes) -> None: ... + +# Possibly omitted arg, but we now only issue one error +convert4("hello", 3.15, b'') # E: Missing positional argument "first" in call to "convert4" + +# Other cases +convert4("hello") # E: Missing positional arguments "second", "third", "fourth" in call to "convert4" \ + # E: Argument 1 to "convert4" has incompatible type "str"; expected "int" + +convert4("hello", 3.15) # E: Missing positional arguments "third", "fourth" in call to "convert4" \ + # E: Argument 1 to "convert4" has incompatible type "str"; expected "int" \ + # E: Argument 2 to "convert4" has incompatible type "float"; expected "str" + +convert4(b'', "hello", 3.15) # E: Missing positional argument "fourth" in call to "convert4" \ + # E: Argument 1 to "convert4" has incompatible type "bytes"; expected "int" +[builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index a3a5b02d54f89..b8f7a5699e199 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -3542,7 +3542,7 @@ reveal_type(C.foo) # N: Revealed type is "def [T] (self: __main__.B[T`1]) -> T` reveal_type(C[int].foo) # N: Revealed type is "def (self: __main__.B[builtins.int]) -> builtins.int" reveal_type(D.foo) # N: Revealed type is "def (self: __main__.B[builtins.int]) -> builtins.int" -[case testDeterminismFromJoinOrderingInSolver] +[case testDeterminismFromJoinOrderingInSolver1] # Used to fail non-deterministically # https://github.com/python/mypy/issues/19121 from __future__ import annotations @@ -3595,6 +3595,46 @@ def draw_none( takes_int_str_none(c3) [builtins fixtures/tuple.pyi] +[case testDeterminismFromJoinOrderingInSolver2] +# Used to fail non-deterministically +# https://github.com/python/mypy/issues/21445 +from typing import Generic, Iterable, TypeVar, overload + +class A: ... + +@overload +def f0(a: A, b: object, /) -> object: ... +@overload +def f0(a: object, b: int, /) -> object: ... +def f0(a, b, /): ... + +@overload +def f1(a: int, b: object, /) -> object: ... +@overload +def f1(a: object, b: A, /) -> object: ... +def f1(a, b, /): ... + +def g(a, b, /): ... + +T = TypeVar("T") +K = TypeVar("K") +V = TypeVar("V") + +class ziplike(Generic[T]): + def __new__(cls, x: str, y: tuple[V, ...], /) -> ziplike[tuple[str, V]]: + raise + def __iter__(self) -> ziplike[T]: + return self + def __next__(self) -> T: + raise + +class dictlike(Generic[K, V]): + def __init__(self, arg: Iterable[tuple[K, V]]) -> None: pass + +x = dictlike(ziplike("012", (f0, f1, g))) +reveal_type(x) # N: Revealed type is "__main__.dictlike[builtins.str, Overload(def (Any, Any) -> Any, def (Any, Any) -> Any, def (Any, Any) -> Any, def (Any, Any) -> Any)]" +[builtins fixtures/dict.pyi] + [case testPropertyWithGenericSetter] from typing import TypeVar @@ -3688,3 +3728,12 @@ reveal_type(ok3) # N: Revealed type is "tuple[()]" bad1: list[()] = [] # E: "list" expects 1 type argument, but none given \ # E: Missing type arguments for generic type "list" [builtins fixtures/tuple.pyi] + +[case testGenericClassAsArgumentToType] +from typing import TypeVar, Generic + +T = TypeVar("T") +def test(tp: type[T]) -> T: ... + +class C(Generic[T]): ... +reveal_type(test(C)) # N: Revealed type is "__main__.C[Any]" diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 6911a350376f3..18931dd9f152f 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5188,7 +5188,7 @@ class C: [builtins fixtures/tuple.pyi] [out] [out2] -tmp/a.py:3: note: Revealed type is "tuple[builtins.int, fallback=b.C.Hidden@5]" +tmp/a.py:3: note: Revealed type is "tuple[builtins.int, fallback=b.Hidden@5]" [case testIncrementalNodeCreatedFromGetattr] import a @@ -6892,7 +6892,7 @@ class C: [typing fixtures/typing-typeddict.pyi] [out] [out2] -tmp/a.py:3: note: Revealed type is "TypedDict('b.C.Hidden@5', {'x': builtins.int})" +tmp/a.py:3: note: Revealed type is "TypedDict('b.Hidden@5', {'x': builtins.int})" [case testNoIncrementalCrashOnInvalidEnumMethod] import a @@ -6916,7 +6916,7 @@ class TheClass: [builtins fixtures/tuple.pyi] [out] [out2] -tmp/a.py:3: note: Revealed type is "def (value: builtins.object) -> lib.TheClass.pyenum@6" +tmp/a.py:3: note: Revealed type is "def (value: builtins.object) -> lib.pyenum@6" -- Note: do not use _no_parallel unless really needed! [case testIncrementalFunctoolsPartial_no_parallel] @@ -7995,3 +7995,228 @@ import mod [out2] main:2: error: Cannot find implementation or library stub for module named "mod" main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +[case testIncrementalFileConfigCommentsStale] +-- When a dependency changes, the importing module becomes stale and is +-- reprocessed via process_stale_scc. As inline config comments are not cached +-- (by design), moving the order of processing the stale SCC can accidentally +-- break file config comments on subsequent runs. +# mypy: disable-error-code="import-not-found" +import nonexistent +import b +[file b.py] +x = 1 +[file b.py.2] +x = "hello" +[builtins fixtures/module.pyi] +[stale b] +[rechecked b] + +[case testNamedTupleInlineBaseClassNoNameCollision] +import m +[file m.py] +from lib import A, B +a: A +b: B +reveal_type(a) +reveal_type(b) +[file m.py.2] +from lib import A, B +a: A +b: B +reveal_type(a) # touch +reveal_type(b) +[file lib.py] +from typing import NamedTuple +class A(NamedTuple("Same", [('x', int)])): pass +class B(NamedTuple("Same", [('x', str)])): pass +[builtins fixtures/tuple.pyi] +[out] +tmp/m.py:4: note: Revealed type is "tuple[builtins.int, fallback=lib.A]" +tmp/m.py:5: note: Revealed type is "tuple[builtins.str, fallback=lib.B]" +[out2] +tmp/m.py:4: note: Revealed type is "tuple[builtins.int, fallback=lib.A]" +tmp/m.py:5: note: Revealed type is "tuple[builtins.str, fallback=lib.B]" + +[case testNamedTupleInlineBaseClassRedefined] +import m +[file m.py] +from lib import A +a: A +reveal_type(a) +[file m.py.2] +from lib import A +a: A # touch +reveal_type(a) +[file lib.py] +from typing import NamedTuple +class A(NamedTuple("N", [('x', int)])): pass +class A(NamedTuple("N", [('x', str)])): pass +[builtins fixtures/tuple.pyi] +[out] +tmp/lib.py:3: error: Name "A" already defined on line 2 +tmp/m.py:3: note: Revealed type is "tuple[builtins.int, fallback=lib.A]" +[out2] +tmp/lib.py:3: error: Name "A" already defined on line 2 +tmp/m.py:3: note: Revealed type is "tuple[builtins.int, fallback=lib.A]" + +[case testTypedDictInlineBaseClassRedefined] +import m +[file m.py] +from lib import A +a: A +reveal_type(a) +[file m.py.2] +from lib import A +a: A # touch +reveal_type(a) +[file lib.py] +from typing import TypedDict +class A(TypedDict("TD", {'x': int})): + pass +class A(TypedDict("TD", {'x': str})): + pass +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] +[out] +tmp/lib.py:4: error: Name "A" already defined (possibly by an import) +tmp/m.py:3: note: Revealed type is "TypedDict('lib.A', {'x': builtins.int})" +[out2] +tmp/lib.py:4: error: Name "A" already defined (possibly by an import) +tmp/m.py:3: note: Revealed type is "TypedDict('lib.A', {'x': builtins.int})" + +[case testNewReturnTypeIncremental] +import m +[file m.py] +from lib import C + +reveal_type(C().ta) +x: int = C().ta +reveal_type(C().ta.x) +[file m.py.2] +from lib import C + +# touch +reveal_type(C().ta) +x: int = C().ta +reveal_type(C().ta.x) +[file lib.py] +class A: + x = "test" + def __new__(cls) -> int: ... # type: ignore + +class C: + def __init__(self) -> None: + self.ta = A +[out] +tmp/m.py:3: note: Revealed type is "def () -> builtins.int" +tmp/m.py:4: error: Incompatible types in assignment (expression has type "type[A]", variable has type "int") +tmp/m.py:5: note: Revealed type is "builtins.str" +[out2] +tmp/m.py:4: note: Revealed type is "def () -> builtins.int" +tmp/m.py:5: error: Incompatible types in assignment (expression has type "type[A]", variable has type "int") +tmp/m.py:6: note: Revealed type is "builtins.str" + +[case testTypeFormWorksInIncrementalMode] +import b +[file a.py] +from typing import TypeVar +from typing_extensions import TypeForm + +T = TypeVar("T") +def test(arg: TypeForm[T]) -> T: ... + +[file b.py] +from a import test +test("int") + +[file b.py.2] +from a import test +reveal_type(test("int")) + +[builtins fixtures/primitives.pyi] +[typing fixtures/typing-full.pyi] +[out] +[out2] +tmp/b.py:2: note: Revealed type is "builtins.int" + +[case testIncrementalNativeParserShadowFile] +# flags: --native-parser --shadow-file tmp/a.py tmp/a_shadow.py +import a +import b +-- a and b don't depend on each other, so they can be checked in parallel. +[file a.py] +x: int = 1 +reveal_type(x) +[file b.py] +y: int = 2 +[file a_shadow.py] +x: str = "y" +reveal_type(x) +[rechecked] +[stale] +[out1] +tmp/a.py:2: note: Revealed type is "builtins.str" +[out2] +tmp/a.py:2: note: Revealed type is "builtins.str" + +[case testIncrementalNativeParserShadowFileChanged] +# flags: --native-parser --shadow-file tmp/a.py tmp/a_shadow.py +import a +import b +-- a and b don't depend on each other, so they can be checked in parallel. +[file a.py] +x: int = 1 +reveal_type(x) +[file b.py] +y: int = 2 +[file a_shadow.py] +x: str = "y" +reveal_type(x) +[file a_shadow.py.2] +x: bytes = b"y" +reveal_type(x) +[rechecked a] +[stale a] +[out1] +tmp/a.py:2: note: Revealed type is "builtins.str" +[out2] +tmp/a.py:2: note: Revealed type is "builtins.bytes" + +[case testIncrementalNativeParserShadowFileIntroduced] +# flags: --native-parser +# flags2: --native-parser --shadow-file tmp/a.py tmp/a_shadow.py +import a +import b +-- a and b don't depend on each other, so they can be checked in parallel. +-- The first run has no shadow file; the second run introduces one for a.py. +[file a.py] +x: int = 1 +reveal_type(x) +[file b.py] +y: int = 2 +[file a_shadow.py.2] +x: str = "y" +reveal_type(x) +[rechecked a] +[stale a] +[out1] +tmp/a.py:2: note: Revealed type is "builtins.int" +[out2] +tmp/a.py:2: note: Revealed type is "builtins.str" + +[case testIncrementalFollowImportsSkipStubWithSkippedRuntimeDependency] +# flags: --follow-imports=skip +import pkg.foo +[file pkg/__init__.py] + +[file pkg/foo.pyi] +from pkg import helper + +x = 1 +[file pkg/helper.py] +y = 2 +[rechecked] +[stale] +[out2] +[out3] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 029224e122168..acd81839fcdc7 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2254,7 +2254,6 @@ if y not in x: else: reveal_type(y) # N: Revealed type is "builtins.int" [builtins fixtures/list.pyi] -[out] [case testNarrowTypeAfterInListOfOptional] # flags: --warn-unreachable @@ -2268,7 +2267,6 @@ if y not in x: else: reveal_type(y) # N: Revealed type is "builtins.int | None" [builtins fixtures/list.pyi] -[out] [case testNarrowTypeAfterInListNonOverlapping] # flags: --warn-unreachable @@ -2294,18 +2292,16 @@ def f(x: Optional[int], lst: Optional[List[int]], nested_any: List[List[Any]]) - [case testNarrowTypeAfterInTuple] # flags: --warn-unreachable -from typing import Optional class A: pass class B(A): pass class C(A): pass -y: Optional[B] -if y in (B(), C()): - reveal_type(y) # N: Revealed type is "__main__.B" -else: - reveal_type(y) # N: Revealed type is "__main__.B | None" +def f(y: B | None): + if y in (B(), C()): + reveal_type(y) # N: Revealed type is "__main__.B" + else: + reveal_type(y) # N: Revealed type is "__main__.B | None" [builtins fixtures/tuple.pyi] -[out] [case testNarrowTypeAfterInNamedTuple] # flags: --warn-unreachable @@ -2321,7 +2317,6 @@ if y not in nt: else: reveal_type(y) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] -[out] [case testNarrowTypeAfterInDict] # flags: --warn-unreachable @@ -2338,7 +2333,6 @@ if y not in x: else: reveal_type(y) # N: Revealed type is "builtins.str" [builtins fixtures/dict.pyi] -[out] [case testNarrowTypeAfterInNoAnyOrObject] # flags: --warn-unreachable @@ -2358,7 +2352,6 @@ else: reveal_type(y) # N: Revealed type is "builtins.int | None" [typing fixtures/typing-medium.pyi] [builtins fixtures/list.pyi] -[out] [case testNarrowTypeAfterInUserDefined] # flags: --warn-unreachable @@ -2380,7 +2373,6 @@ else: reveal_type(y) # N: Revealed type is "builtins.int | None" [typing fixtures/typing-full.pyi] [builtins fixtures/list.pyi] -[out] [case testNarrowTypeAfterInSet] # flags: --warn-unreachable @@ -2397,7 +2389,6 @@ if y not in s: else: reveal_type(y) # N: Revealed type is "builtins.str" [builtins fixtures/set.pyi] -[out] [case testNarrowTypeAfterInTypedDict] # flags: --warn-unreachable @@ -2414,7 +2405,6 @@ def f() -> None: reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[out] [case testIsinstanceWidensWithAnyArg] # flags: --warn-unreachable diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index f11c2b6f4fc40..6f5e11d85ee48 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -495,6 +495,21 @@ def f(a: int, *, b: str) -> None: pass f(1) # E: Missing named argument "b" for "f" +[case testTooManyPositionalArgumentsFromOtherModule] +import m +m.f(1, 2) +[file m.py] +def f(a: int, *, b: int) -> None: + pass +[out] +main:2: error: Too many positional arguments for "f" +main:2: note: "f" defined in "m" + +[case testTooManyPositionalArgumentsForSameModule] +def f(a: int, *, b: int) -> None: + pass +f(1, 2) # E: Too many positional arguments for "f" + [case testStarArgsAndKwArgsSpecialCase] from typing import Dict, Mapping diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 285ae92325d8e..158b95aa598d0 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -132,6 +132,7 @@ main:5: error: Argument "rename" to "namedtuple" has incompatible type "str"; ex main:6: error: Unexpected keyword argument "unrecognized_arg" for "namedtuple" main:6: note: "namedtuple" defined in "collections" main:7: error: Too many positional arguments for "namedtuple" +main:7: note: "namedtuple" defined in "collections" [case testNamedTupleDefaults] from collections import namedtuple diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 4f23d9147205e..29f4cd47929d9 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -3201,7 +3201,7 @@ class X: [builtins fixtures/dict.pyi] -[case testTypeNarrowingStringInLiteralContainer] +[case testNarrowStringInLiteralContainer] # flags: --strict-equality --warn-unreachable from typing import Literal @@ -3235,6 +3235,123 @@ def narrow_set(x: str, t: set[Literal['a', 'b']]): reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] +[case testNarrowLiteralInLiteralContainer] +# flags: --strict-equality --warn-unreachable +from typing import Literal + +def narrow_tuple_exact(x: Literal['a', 'b', 'c'], t: tuple[Literal['a'], Literal['b']]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['c']" + + if x not in t: + reveal_type(x) # N: Revealed type is "Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + +def narrow_tuple_expression(x: Literal['a', 'b', 'c']): + # TODO: this should match narrow_tuple_exact + if x in ('a', 'b'): + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x not in ('a', 'b'): + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + +def narrow_tuple_union(x: Literal['a', 'b', 'c'], t: tuple[Literal['a', 'b']]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x not in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + +def narrow_tuple_with_other_type(x: Literal['a', 'b', 'c'], t: tuple[Literal['a'], int]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a']" + else: + reveal_type(x) # N: Revealed type is "Literal['b'] | Literal['c']" + +def narrow_homo_tuple(x: Literal['a', 'b', 'c'], t: tuple[Literal['a', 'b'], ...]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + +def narrow_list(x: Literal['a', 'b', 'c'], t: list[Literal['a', 'b']]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in ['a', 'b']: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in ['a', 'b', *[]]: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + +def narrow_set(x: Literal['a', 'b', 'c'], t: set[Literal['a', 'b']]): + if x in t: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in {'a', 'b'}: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in {'a', 'b', *[]}: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + +def narrow_dict(x: Literal['a', 'b', 'c'], t: dict[Literal['a', 'b'], int]): + if x in t: # E: Unsupported operand types for in ("Literal['a', 'b', 'c']" and "dict[Literal['a', 'b'], int]") + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in {'a': 0, 'b': 1}: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + + if x in {'a': 0, 'b': 1, **{}}: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" + else: + reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b'] | Literal['c']" +[builtins fixtures/primitives.pyi] + + +[case testNarrowCustomEqEnumInLiteralContainer] +# flags: --strict-equality --warn-unreachable +# https://github.com/python/mypy/issues/21703 +from enum import Enum + +class E(Enum): + foo = 1 + bar = 2 + + def __eq__(self, other: object) -> bool: + return True + +def f(x: int) -> None: + if x in [E.foo, E.bar]: + reveal_type(x) # N: Revealed type is "builtins.int" +[builtins fixtures/list.pyi] + [case testNarrowingLiteralInLiteralContainer] # flags: --strict-equality --warn-unreachable diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 4148d04014a81..cfb59a9f8e9ba 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -936,8 +936,8 @@ class C: self.o: Out c = C() -reveal_type(c.o) # N: Revealed type is "tuple[tuple[builtins.str, __main__.Other@7, fallback=__main__.C.In@6], __main__.Other@7, fallback=__main__.C.Out@5]" -reveal_type(c.o.x) # N: Revealed type is "tuple[builtins.str, __main__.Other@7, fallback=__main__.C.In@6]" +reveal_type(c.o) # N: Revealed type is "tuple[tuple[builtins.str, __main__.Other@7, fallback=__main__.In@6], __main__.Other@7, fallback=__main__.Out@5]" +reveal_type(c.o.x) # N: Revealed type is "tuple[builtins.str, __main__.Other@7, fallback=__main__.In@6]" [builtins fixtures/tuple.pyi] [case testNewAnalyzerNamedTupleClassNestedMethod] @@ -956,9 +956,9 @@ class C: self.o: Out c = C() -reveal_type(c.o) # N: Revealed type is "tuple[tuple[builtins.str, __main__.Other@12, fallback=__main__.C.In@9], __main__.Other@12, fallback=__main__.C.Out@5]" -reveal_type(c.o.x) # N: Revealed type is "tuple[builtins.str, __main__.Other@12, fallback=__main__.C.In@9]" -reveal_type(c.o.method()) # N: Revealed type is "tuple[builtins.str, __main__.Other@12, fallback=__main__.C.In@9]" +reveal_type(c.o) # N: Revealed type is "tuple[tuple[builtins.str, __main__.Other@12, fallback=__main__.In@9], __main__.Other@12, fallback=__main__.Out@5]" +reveal_type(c.o.x) # N: Revealed type is "tuple[builtins.str, __main__.Other@12, fallback=__main__.In@9]" +reveal_type(c.o.method()) # N: Revealed type is "tuple[builtins.str, __main__.Other@12, fallback=__main__.In@9]" [builtins fixtures/tuple.pyi] [case testNewAnalyzerNamedTupleClassForwardMethod] @@ -3228,3 +3228,16 @@ def deco(fn: Callable[[], T]) -> Callable[[], T]: ... @deco def defer() -> int: ... + +[case testNestedClassSelfNoPlaceholder] +from typing import Generic, TypeVar + +def test() -> None: + T = TypeVar("T", bound="Model") + + class Query(Generic[T]): + def filter(self, value: T) -> None: + ... + + class Model: + pass diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index a0789596a4798..95042d1393ab1 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -194,7 +194,7 @@ def func() -> None: A = NewType('A', str) B = NewType('B', str) - a = A(3) # E: Argument 1 to "A@6" has incompatible type "int"; expected "str" + a = A(3) # E: Argument 1 to "A" has incompatible type "int"; expected "str" a = A('xyz') b = B('xyz') @@ -206,7 +206,6 @@ class MyClass: b = A(3) c = MyClass.C(3.5) -[out] [case testNewTypeInMultipleFiles] import a diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 970ba45d0e8e2..d1e928441a9ec 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2742,16 +2742,16 @@ def f8(x: int, /, **kwargs: str) -> int: def f9(x: int, **kwargs: Unpack[Opt]) -> int: return 0 -reveal_type(Sneaky(f1).kwargs) # N: Revealed type is "builtins.dict[builtins.str, Never]" -reveal_type(Sneaky(f2, 1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int})" -reveal_type(Sneaky(f3, 1).kwargs) # N: Revealed type is "builtins.dict[builtins.str, Never]" -reveal_type(Sneaky(f4, x=1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x': builtins.int})" -reveal_type(Sneaky(f5, 1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y'?: builtins.int})" -reveal_type(Sneaky(f5, 1, 2).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y'?: builtins.int})" +reveal_type(Sneaky(f1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {}, closed=True)" +reveal_type(Sneaky(f2, 1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int}, closed=True)" +reveal_type(Sneaky(f3, 1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {}, closed=True)" +reveal_type(Sneaky(f4, x=1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x': builtins.int}, closed=True)" +reveal_type(Sneaky(f5, 1).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y'?: builtins.int}, closed=True)" +reveal_type(Sneaky(f5, 1, 2).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y'?: builtins.int}, closed=True)" reveal_type(Sneaky(f6, x=1).kwargs) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]" reveal_type(Sneaky(f6, x=1, y=2).kwargs) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]" reveal_type(Sneaky(f7, 1, y='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int})" reveal_type(Sneaky(f8, 1, y='').kwargs) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" -reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" -reveal_type(Sneaky(f9, 1, y=0, z='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str})" +reveal_type(Sneaky(f9, 1, y=0).kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str}, closed=True)" +reveal_type(Sneaky(f9, 1, y=0, z='').kwargs) # N: Revealed type is "TypedDict('builtins.dict', {'x'?: builtins.int, 'y': builtins.int, 'z'?: builtins.str}, closed=True)" [builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 87fd30e437170..6e86507eb48d7 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4779,3 +4779,33 @@ class A(Protocol): pass [builtins fixtures/tuple.pyi] + +[case testTupleTypeSelfTypeProto] +from typing import Protocol, TypeVar + +R = TypeVar("R", covariant=True) + +class P(Protocol[R]): + def rep(self) -> R: ... + +T = TypeVar("T") +def rep(x: P[T]) -> T: ... + +class C(tuple[int, str]): + def rep(self: T) -> T: ... + +t: C +reveal_type(t) # N: Revealed type is "tuple[builtins.int, builtins.str, fallback=__main__.C]" +reveal_type(rep(t)) # N: Revealed type is "tuple[builtins.int, builtins.str, fallback=__main__.C]" + +def ok_rep(x: P[tuple[int, str]]) -> None: ... +ok_rep(t) + +def bad_rep(x: P[tuple[str, int]]) -> None: ... +bad_rep(t) # E: Argument 1 to "bad_rep" has incompatible type "C"; expected "P[tuple[str, int]]" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def rep(self) -> tuple[str, int] \ + # N: Got: \ + # N: def rep(self) -> C +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 03a0934b662c6..2f5b2d7ce8e86 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -3531,7 +3531,7 @@ def switch(choice: type[T_Choice]) -> None: reveal_type(choice()) # N: Revealed type is "b.Two" case _: reveal_type(choice) # N: Revealed type is "type[T_Choice`-1]" - reveal_type(choice()) # N: Revealed type is "b.One | b.Two" + reveal_type(choice()) # N: Revealed type is "T_Choice`-1" [file b.py] class One: ... diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index b51186796b8f0..9d612109d5452 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -824,6 +824,20 @@ f(1, u) f('x', None) # E: Value of type variable "T" of "f" cannot be "str" \ # E: Value of type variable "S" of "f" cannot be "None" +[case testPEP695UpperBoundTypeTypeConstructorReturnType] +class A: + def __init__(self, value: str = "") -> None: pass +class B: + def __init__(self, value: str = "") -> None: pass + +def make[T: A | B](ftype: type[T], value: str | None) -> T: + if value is None: + return ftype() + return ftype(value) + +reveal_type(make(A, "a")) # N: Revealed type is "__main__.A" +reveal_type(make(B, None)) # N: Revealed type is "__main__.B" + [case testPEP695InferVarianceOfTupleType] class Cov[T](tuple[int, str]): def f(self) -> T: pass @@ -2280,3 +2294,40 @@ class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \ # E: Can only use one type var tuple in a class def pass [builtins fixtures/tuple.pyi] + +[case testPEP695VariadicAliasUnpack] +class C[*Ts]: + pass + +type T[T, *Ts] = C[*Ts] + +x: T[bool, *tuple[()]] +reveal_type(x) # N: Revealed type is "__main__.C[()]" + +y: T[bool] +reveal_type(y) # N: Revealed type is "__main__.C[()]" +[builtins fixtures/tuple.pyi] + +[case testTupleBaseTupleTypeUnpack] +class X(tuple[*tuple[int], *tuple[int]]): + pass +x = X((1, 2)) +reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.X]" +[builtins fixtures/tuple.pyi] + +[case testNewTypeTupleTypeUnpack] +from typing import NewType + +T = NewType("T", tuple[*tuple[int], *tuple[int]]) +t = T((1, 2)) +reveal_type(t) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.T]" +[builtins fixtures/tuple.pyi] + +[case testNamedTupleTupleTypeUnpack] +from typing import NamedTuple + +class N(NamedTuple): + x: tuple[*tuple[int], *tuple[int]] +n = N((1, 2)) +reveal_type(n) # N: Revealed type is "tuple[tuple[builtins.int, builtins.int], fallback=__main__.N]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-python313.test b/test-data/unit/check-python313.test index 8a80977fb22a9..d52cb575bc916 100644 --- a/test-data/unit/check-python313.test +++ b/test-data/unit/check-python313.test @@ -140,7 +140,7 @@ reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)" reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)" def func_c1[*Ts = *tuple[int, str]](x: int | Callable[[*Ts], None]) -> tuple[*Ts]: ... -# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[str]" # TODO +reveal_type(func_c1(callback1)) # N: Revealed type is "tuple[builtins.str]" reveal_type(func_c1(2)) # N: Revealed type is "tuple[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] @@ -195,7 +195,7 @@ def func_c1( a: ClassC1, b: ClassC1[float], ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]" k = ClassC1() @@ -250,7 +250,7 @@ def func_c1( a: TC1, b: TC1[float], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.float]" [builtins fixtures/tuple.pyi] @@ -291,7 +291,7 @@ reveal_type(A2().x) # N: Revealed type is "builtins.int" reveal_type(A3().x) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] -[case testTypeVarDefaultToAnotherTypeVar] +[case testTypeVarDefaultToAnotherTypeVarClass] class A[X, Y = X, Z = Y]: x: X y: Y @@ -300,8 +300,7 @@ class A[X, Y = X, Z = Y]: a1: A[int] reveal_type(a1.x) # N: Revealed type is "builtins.int" reveal_type(a1.y) # N: Revealed type is "builtins.int" -# TODO: this must reveal `int` as well: -reveal_type(a1.z) # N: Revealed type is "X`1" +reveal_type(a1.z) # N: Revealed type is "builtins.int" a2: A[int, str] reveal_type(a2.x) # N: Revealed type is "builtins.int" @@ -314,6 +313,35 @@ reveal_type(a3.y) # N: Revealed type is "builtins.str" reveal_type(a3.z) # N: Revealed type is "builtins.bool" [builtins fixtures/tuple.pyi] +[case testTypeVarDefaultToAnotherTypeVarAlias] +type A[X, Y = X, Z = Y] = tuple[X, Y, Z] + +a1: A[int] +reveal_type(a1[0]) # N: Revealed type is "builtins.int" +reveal_type(a1[1]) # N: Revealed type is "builtins.int" +reveal_type(a1[2]) # N: Revealed type is "builtins.int" + +a2: A[int, str] +reveal_type(a2[0]) # N: Revealed type is "builtins.int" +reveal_type(a2[1]) # N: Revealed type is "builtins.str" +reveal_type(a2[2]) # N: Revealed type is "builtins.str" + +a3: A[int, str, bool] +reveal_type(a3[0]) # N: Revealed type is "builtins.int" +reveal_type(a3[1]) # N: Revealed type is "builtins.str" +reveal_type(a3[2]) # N: Revealed type is "builtins.bool" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultAssertTypeType] +from typing import assert_type + +class NoNonDefaults[T = str, S = int]: ... + +assert_type(NoNonDefaults[()], type[NoNonDefaults[str, int]]) +assert_type(NoNonDefaults[str], type[NoNonDefaults[str, int]]) +assert_type(NoNonDefaults[str, int], type[NoNonDefaults[str, int]]) +[builtins fixtures/tuple.pyi] + [case testTypeVarDefaultToAnotherTypeVarWrong] class A[Y = X, X = int]: ... # E: Name "X" is not defined @@ -337,3 +365,118 @@ type Result[T, E] = Ok[T, E] | Err[E, T] class Bar[U]: def foo(data: U, cond: bool) -> Result[U, str]: return Ok(data) if cond else Err("Error") + +[case testRecursiveTypeVarDefaultBasicNewStyle] +class C[T: C = C]: + pass + +c: C +reveal_type(c) # N: Revealed type is "__main__.C[__main__.C[Any]]" + +[case testRecursiveTypeVarDefaultMutualNewStyle] +class C[T = D]: + pass + +class D[S = C]: + pass + +c: C +d: D +reveal_type(c) # N: Revealed type is "__main__.C[__main__.D[Any]]" +reveal_type(d) # N: Revealed type is "__main__.D[__main__.C[Any]]" + +[case testNonRecursiveSimpleTypeVarDefaultNewStyle] +class Child[S = Parent]: ... + +class Parent[T = int]: ... + +reveal_type(Child()) # N: Revealed type is "__main__.Child[__main__.Parent[builtins.int]]" + +[case testNonRecursiveTypeVarDefaultImportCycleClassNewStyle] +import exp +[file exp.pyi] +import ind + +class F[T: D = D]: + x: T + +class D(E): ... +class E: ... + +[file ind.pyi] +from exp import F + +class Ind(F): ... +x: Ind +reveal_type(x.x) # N: Revealed type is "exp.D" + +[case testNonRecursiveTypeVarDefaultImportCycleAliasNewStyle] +import exp +[file exp.pyi] +import ind + +type F[T: D = D] = list[T] + +class D(E): ... +class E: ... + +[file ind.pyi] +from exp import F + +type Ind = list[F] +x: Ind +reveal_type(x) # N: Revealed type is "builtins.list[builtins.list[exp.D]]" + +[case testRecursiveTypeVarDefaultClassAndAliasNewStyle] +class Trait[T = Pattern]: + pass + +class Pattern1(Trait): + pass +class Pattern2(Trait): + pass + +type Pattern = Pattern1 | Pattern2 + +reveal_type(Trait()) # N: Revealed type is "__main__.Trait[__main__.Pattern1 | __main__.Pattern2]" +[builtins fixtures/tuple.pyi] + +[case testRecursiveTypeVarDefaultOnlyAliasNewStyle] +type A[T = A] = list[T] +a: A +reveal_type(a) # N: Revealed type is "builtins.list[builtins.list[Any]]" +[builtins fixtures/tuple.pyi] + +[case testRecursiveAliasTypeVarDefaultNewStyle] +type A[T = A] = int +a: A +reveal_type(a) # N: Revealed type is "builtins.int" + +[case testPEP695VariadicGenericClassEmptyTupleUnpackIndex] +class Blah[*Ts]: + pass + +x: Blah[()] +y: Blah[*tuple[()]] + +type Empty = tuple[()] +z: Blah[*Empty] + +reveal_type(x) # N: Revealed type is "__main__.Blah[()]" +reveal_type(y) # N: Revealed type is "__main__.Blah[()]" +reveal_type(z) # N: Revealed type is "__main__.Blah[()]" +[builtins fixtures/tuple.pyi] + +[case testPEP695VariadicGenericAliasEmptyTupleUnpackIndex] +type Blah[*Ts] = list[tuple[*Ts]] + +x: Blah[()] +y: Blah[*tuple[()]] + +type Empty = tuple[()] +z: Blah[*Empty] + +reveal_type(x) # N: Revealed type is "builtins.list[tuple[()]]" +reveal_type(y) # N: Revealed type is "builtins.list[tuple[()]]" +reveal_type(z) # N: Revealed type is "builtins.list[tuple[()]]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-recursive-types.test b/test-data/unit/check-recursive-types.test index 10333d113a689..9eff3d98fbe09 100644 --- a/test-data/unit/check-recursive-types.test +++ b/test-data/unit/check-recursive-types.test @@ -748,8 +748,8 @@ tdb: TDB T = TypeVar("T") def f(x: T, y: T) -> T: ... # Join for recursive types is very basic, but just add tests that we don't crash. -reveal_type(f(tda1, tda2)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': TypedDict('__main__.TDA1', {'x': builtins.int, 'y': ...})})" -reveal_type(f(tda1, tdb)) # N: Revealed type is "TypedDict({})" +reveal_type(f(tda1, tda2)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': TypedDict('__main__.TDA2', {'x': builtins.int, 'y': ...})})" +reveal_type(f(tda1, tdb)) # N: Revealed type is "TypedDict({'x'=: builtins.object, 'y'=: builtins.object})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 7a257bc017c36..f504e00688cff 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -729,13 +729,13 @@ class C: self.c = A [builtins fixtures/tuple.pyi] [out1] -main:2: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.C.A@4]" -main:3: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.C.A@4]" -main:4: note: Revealed type is "def (x: builtins.int) -> tuple[builtins.int, fallback=ntcrash.C.A@4]" +main:2: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.A@4]" +main:3: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.A@4]" +main:4: note: Revealed type is "def (x: builtins.int) -> tuple[builtins.int, fallback=ntcrash.A@4]" [out2] -main:2: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.C.A@4]" -main:3: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.C.A@4]" -main:4: note: Revealed type is "def (x: builtins.int) -> tuple[builtins.int, fallback=ntcrash.C.A@4]" +main:2: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.A@4]" +main:3: note: Revealed type is "tuple[builtins.int, fallback=ntcrash.A@4]" +main:4: note: Revealed type is "def (x: builtins.int) -> tuple[builtins.int, fallback=ntcrash.A@4]" -- -- Strict optional @@ -1066,13 +1066,13 @@ class C: [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [out1] -main:2: note: Revealed type is "TypedDict('ntcrash.C.A@4', {'x': builtins.int})" -main:3: note: Revealed type is "TypedDict('ntcrash.C.A@4', {'x': builtins.int})" -main:4: note: Revealed type is "def (*, x: builtins.int) -> TypedDict('ntcrash.C.A@4', {'x': builtins.int})" +main:2: note: Revealed type is "TypedDict('ntcrash.A@4', {'x': builtins.int})" +main:3: note: Revealed type is "TypedDict('ntcrash.A@4', {'x': builtins.int})" +main:4: note: Revealed type is "def (*, x: builtins.int) -> TypedDict('ntcrash.A@4', {'x': builtins.int})" [out2] -main:2: note: Revealed type is "TypedDict('ntcrash.C.A@4', {'x': builtins.int})" -main:3: note: Revealed type is "TypedDict('ntcrash.C.A@4', {'x': builtins.int})" -main:4: note: Revealed type is "def (*, x: builtins.int) -> TypedDict('ntcrash.C.A@4', {'x': builtins.int})" +main:2: note: Revealed type is "TypedDict('ntcrash.A@4', {'x': builtins.int})" +main:3: note: Revealed type is "TypedDict('ntcrash.A@4', {'x': builtins.int})" +main:4: note: Revealed type is "def (*, x: builtins.int) -> TypedDict('ntcrash.A@4', {'x': builtins.int})" [case testSerializeNonTotalTypedDict] from m import d @@ -1088,6 +1088,20 @@ main:2: note: Revealed type is "TypedDict('m.D', {'x'?: builtins.int, 'y'?: buil [out2] main:2: note: Revealed type is "TypedDict('m.D', {'x'?: builtins.int, 'y'?: builtins.str})" +[case testSerializeClosedTotalTypedDict] +from m import d +reveal_type(d) +[file m.py] +from typing import TypedDict +D = TypedDict('D', {'x': int, 'y': str}, closed=True) +d: D +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] +[out1] +main:2: note: Revealed type is "TypedDict('m.D', {'x': builtins.int, 'y': builtins.str}, closed=True)" +[out2] +main:2: note: Revealed type is "TypedDict('m.D', {'x': builtins.int, 'y': builtins.str}, closed=True)" + -- -- Modules -- diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 4d68f93a21eda..e48700c69e38e 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -1112,6 +1112,7 @@ reveal_type(t3) # N: Revealed type is "Any" T4 = TypeAliasType("T4") # E: Missing positional argument "value" in call to "TypeAliasType" T5 = TypeAliasType("T5", int, str) # E: Too many positional arguments for "TypeAliasType" \ + # N: "TypeAliasType" defined in "typing_extensions" \ # E: Argument 3 to "TypeAliasType" has incompatible type "type[str]"; expected "tuple[TypeVar? | ParamSpec? | TypeVarTuple?, ...]" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 622004758364b..01414ee2f457a 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -235,6 +235,20 @@ reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testTypedDictDecoratorUndefinedNames] +from typing import TypedDict + +@abc # E: Name "abc" is not defined +@efg.hij # E: Name "efg" is not defined +@klm[nop] # E: Name "klm" is not defined \ + # E: Name "nop" is not defined +@qrs.tuv[wxy] # E: Name "qrs" is not defined \ + # E: Name "wxy" is not defined +class A(TypedDict): + x: int +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictWithClassmethodAlternativeConstructorDoesNotCrash] # https://github.com/python/mypy/issues/5653 from typing import TypedDict @@ -299,7 +313,7 @@ class Point1(TypedDict): x: int class Point2(TypedDict): x: float -class Bad(Point1, Point2): # E: Overwriting TypedDict field "x" while merging +class Bad(Point1, Point2): # E: Incompatible definitions of field "x" in base classes "Point2" and "Point1" pass b: Bad @@ -313,13 +327,82 @@ from typing import TypedDict class Point1(TypedDict): x: int class Point2(Point1): - x: float # E: Overwriting TypedDict field "x" while extending + x: float # E: Definition of field "x" incompatible with base class "Point1" p2: Point2 reveal_type(p2) # N: Revealed type is "TypedDict('__main__.Point2', {'x': builtins.float})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testCanCreateTypedDictWithClassOverwriting3] +from typing import TypedDict + +class Point1(TypedDict): + x: float +class Point2(Point1): + x: int # E: Definition of field "x" incompatible with base class "Point1" + +p2: Point2 +reveal_type(p2) # N: Revealed type is "TypedDict('__main__.Point2', {'x': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanCreateTypedDictWithClassOverwriting4] +from typing import TypedDict + +class Point1(TypedDict): + x: int +class Point2(TypedDict, total=False): + x: int +class Bad(Point1, Point2): # E: Field "x" is required in base class "Point1" but can be deleted in base class "Point2" + pass + +b: Bad +reveal_type(b) # N: Revealed type is "TypedDict('__main__.Bad', {'x': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanCreateTypedDictWithClassOverwriting5] +from typing import TypedDict + +class Point1(TypedDict): + x: int +class Point2(Point1, total=False): + x: int # E: Field "x" is required in base class "Point1" + +p2: Point2 +reveal_type(p2) # N: Revealed type is "TypedDict('__main__.Point2', {'x'?: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanCreateTypedDictWithClassOverwriting6] +from typing import TypedDict + +class Point1(TypedDict, total=False): + x: int +class Point2(TypedDict): + x: int +class Bad(Point1, Point2): # E: Field "x" is required in base class "Point2" but can be deleted in base class "Point1" + pass + +b: Bad +reveal_type(b) # N: Revealed type is "TypedDict('__main__.Bad', {'x'?: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanCreateTypedDictWithClassOverwriting7] +from typing import TypedDict + +class Point1(TypedDict, total=False): + x: int +class Point2(Point1): + x: int # E: Field "x" can be deleted in base class "Point1" + +p2: Point2 +reveal_type(p2) # N: Revealed type is "TypedDict('__main__.Point2', {'x': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + -- Subtyping @@ -472,7 +555,7 @@ def f(a: A) -> None: pass l = [a, b] # Join generates an anonymous TypedDict f(l) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x': int})]"; expected "A" ll = [b, c] -f(ll) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x': int, 'z': str})]"; expected "A" +f(ll) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x': int, 'z': str, 'a'=: object})]"; expected "A" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -540,16 +623,28 @@ reveal_type(joined_points) # N: Revealed type is "TypedDict({'x': builtins.int, [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testJoinOfTypedDictRemovesNonequivalentKeys] -from typing import TypedDict +[case testJoinOfTypedDictJoinsNonequivalentKeys] +from typing import TypedDict, NotRequired CellWithInt = TypedDict('CellWithInt', {'value': object, 'meta': int}) -CellWithObject = TypedDict('CellWithObject', {'value': object, 'meta': object}) +CellWithString = TypedDict('CellWithString', {'value': object, 'meta': str}) +CellWithNonRequiredInt = TypedDict('CellWithNonRequiredInt', {'value': object, 'meta': NotRequired[int]}) +CellWithNonRequiredString = TypedDict('CellWithNonRequiredString', {'value': object, 'meta': NotRequired[str]}) c1 = CellWithInt(value=1, meta=42) -c2 = CellWithObject(value=2, meta='turtle doves') -joined_cells = [c1, c2] -reveal_type(c1) # N: Revealed type is "TypedDict('__main__.CellWithInt', {'value': builtins.object, 'meta': builtins.int})" -reveal_type(c2) # N: Revealed type is "TypedDict('__main__.CellWithObject', {'value': builtins.object, 'meta': builtins.object})" -reveal_type(joined_cells) # N: Revealed type is "builtins.list[TypedDict({'value': builtins.object})]" +c2 = CellWithString(value=2, meta='turtle doves') +c3 = CellWithNonRequiredInt(value=1) +c4 = CellWithNonRequiredString(value=2) +j12 = [c1, c2] +j14 = [c1, c4] +j32 = [c3, c2] +j34 = [c3, c4] +reveal_type(c1) # N: Revealed type is "TypedDict('__main__.CellWithInt', {'value': builtins.object, 'meta': builtins.int})" +reveal_type(c2) # N: Revealed type is "TypedDict('__main__.CellWithString', {'value': builtins.object, 'meta': builtins.str})" +reveal_type(c3) # N: Revealed type is "TypedDict('__main__.CellWithNonRequiredInt', {'value': builtins.object, 'meta'?: builtins.int})" +reveal_type(c4) # N: Revealed type is "TypedDict('__main__.CellWithNonRequiredString', {'value': builtins.object, 'meta'?: builtins.str})" +reveal_type(j12) # N: Revealed type is "builtins.list[TypedDict({'value': builtins.object, 'meta'=: builtins.object})]" +reveal_type(j14) # N: Revealed type is "builtins.list[TypedDict({'value': builtins.object, 'meta'?=: builtins.object})]" +reveal_type(j32) # N: Revealed type is "builtins.list[TypedDict({'value': builtins.object, 'meta'?=: builtins.object})]" +reveal_type(j34) # N: Revealed type is "builtins.list[TypedDict({'value': builtins.object, 'meta'?=: builtins.object})]" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -616,6 +711,53 @@ reveal_type(f(g)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': bui [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testMeetOfTypedDictsWithAny] +from typing import Any, TypedDict, TypeVar, Callable +A = TypedDict('A', {'x': Any}) +B = TypedDict('B', {'x': int}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def gAB(x: A, y: B) -> None: pass +def gBA(x: B, y: A) -> None: pass +reveal_type(f(gAB)) # N: Revealed type is "TypedDict({'x': builtins.int})" +reveal_type(f(gBA)) # N: Revealed type is "TypedDict({'x': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictMissingAndAny] +from typing import Any, TypedDict, TypeVar, Callable, ReadOnly +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +A = TypedDict('A', {'x': Any}) +B = TypedDict('B', {'y': Any}) +C = TypedDict('C', {'y': Any}, total=False) +def fAB(x: A, y: B) -> None: pass +def fBA(x: B, y: A) -> None: pass +def fAC(x: A, y: C) -> None: pass +def fCA(x: C, y: A) -> None: pass +if int(): + reveal_type(meet(fAB)) # N: Revealed type is "TypedDict({'x': Any, 'y': Any})" +if int(): + reveal_type(meet(fBA)) # N: Revealed type is "TypedDict({'y': Any, 'x': Any})" +if int(): + reveal_type(meet(fAC)) # N: Revealed type is "TypedDict({'x': Any, 'y'?: Any})" +if int(): + reveal_type(meet(fCA)) # N: Revealed type is "TypedDict({'y'?: Any, 'x': Any})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsNotRequiredNeverAndAny] +from typing import Any, NotRequired, TypedDict, TypeVar, Callable +from typing_extensions import Never +A = TypedDict('A', {'x': NotRequired[Any], 'y': NotRequired[Never]}) +B = TypedDict('B', {'x': NotRequired[Never], 'y': NotRequired[Any]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: A, y: B) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'?: Never, 'y'?: Never})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testMeetOfTypedDictsWithIncompatibleCommonKeysIsUninhabited] from typing import TypedDict, TypeVar, Callable XYa = TypedDict('XYa', {'x': int, 'y': int}) @@ -751,8 +893,8 @@ T = TypeVar('T') def join(x: T, y: T) -> T: return x ab = join(A(x='', y=1, z=''), B(x='', z=1)) ac = join(A(x='', y=1, z=''), C(x='', y=0, z=1)) -ab['y'] # E: "y" is not a valid TypedDict key; expected one of ("x") -ac['a'] # E: "a" is not a valid TypedDict key; expected one of ("x", "y") +ab['y'] # E: "y" is not a valid TypedDict key; expected one of ("x", "z") +ac['a'] # E: "a" is not a valid TypedDict key; expected one of ("x", "y", "z") [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -765,6 +907,17 @@ def get_coordinate(p: TaggedPoint, key: str) -> Union[str, int]: [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testCanGetItemOfTypedDictWithStringLiteralKeyInComprehension] +from typing import TypedDict +Data = TypedDict('Data', {'field1': int, 'field2': str, 'field3': str}) +data: Data +def is_str(value: object) -> bool: ... +filtered_keys1 = [data[key] for key in ("field1", "field2", "field3")] +filtered_keys2 = [key for key in ("field1", "field2", "field3") if is_str(data[key])] +reveal_type(filtered_keys1) # N: Revealed type is "builtins.list[builtins.int | builtins.str]" +reveal_type(filtered_keys2) # N: Revealed type is "builtins.list[builtins.str]" +[builtins fixtures/for.pyi] +[typing fixtures/typing-full.pyi] -- Special Method: __setitem__ @@ -927,6 +1080,18 @@ c: Union[A, B] = {'@type': 'a-type', 'value': 'Test'} # E: Type of TypedDict is [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testTypedDictUnionWithNonTypedDictLiteralKey] +from typing import Union, TypedDict + +class Foo(TypedDict): + some: str + name: str + +baz: Union[Foo, dict[int, float]] = {1: 5.2} +baz2: Union[Foo, dict[int, float]] = {'some': 'x', 'name': 'y'} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + -- Use dict literals [case testTypedDictDictLiterals] @@ -973,10 +1138,10 @@ p4: Point = {'x': 1, 'y': 2} [case testCannotCreateAnonymousTypedDictInstanceUsingDictLiteralWithExtraItems] from typing import TypedDict, TypeVar A = TypedDict('A', {'x': int, 'y': int}) -B = TypedDict('B', {'x': int, 'y': str}) +B = TypedDict('B', {'x': int}) T = TypeVar('T') def join(x: T, y: T) -> T: return x -ab = join(A(x=1, y=1), B(x=1, y='')) +ab = join(A(x=1, y=1), B(x=1)) if int(): ab = {'x': 1, 'z': 1} # E: Expected TypedDict key "x" but found keys ("x", "z") [builtins fixtures/dict.pyi] @@ -990,7 +1155,7 @@ T = TypeVar('T') def join(x: T, y: T) -> T: return x ab = join(A(x=1, y=1, z=1), B(x=1, y=1, z='')) if int(): - ab = {} # E: Expected TypedDict keys ("x", "y") but found no keys + ab = {} # E: Expected TypedDict keys ("x", "y", "z") but found no keys [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -1339,6 +1504,7 @@ A = TypedDict('A', {'x': int}, total=0) # E: "total" argument must be a True or B = TypedDict('B', {'x': int}, total=bool) # E: "total" argument must be a True or False literal C = TypedDict('C', {'x': int}, x=False) # E: Unexpected keyword argument "x" for "TypedDict" D = TypedDict('D', {'x': int}, False) # E: Unexpected arguments to TypedDict() +E = TypedDict('E', {'x': int}, total=False, x=False) # E: Unexpected keyword argument "x" for "TypedDict" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -1411,7 +1577,7 @@ a: A b: B c: C reveal_type(j(a, b)) \ - # N: Revealed type is "TypedDict({})" + # N: Revealed type is "TypedDict({'x'?=: builtins.int})" reveal_type(j(b, b)) \ # N: Revealed type is "TypedDict({'x'?: builtins.int})" reveal_type(j(c, c)) \ @@ -1473,7 +1639,7 @@ def f(a: A) -> None: pass l = [a, b] # Join generates an anonymous TypedDict f(l) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x'?: int})]"; expected "A" ll = [b, c] -f(ll) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x'?: int, 'z'?: str})]"; expected "A" +f(ll) # E: Argument 1 to "f" has incompatible type "list[TypedDict({'x'?: int, 'z'?: str, 'a'?=: object})]"; expected "A" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] @@ -1727,6 +1893,28 @@ reveal_type(x['a']['b']) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testForwardReferenceInOverwrittenKey] +from typing import ReadOnly, TypedDict + +class X("XB"): pass +class Y("YB"): pass +class Z("ZB"): pass + +class A(TypedDict): + key: "X" +class B(A): + key: "Y" # E: Definition of field "key" incompatible with base class "A" +class C(A): + key: "Z" # E: Definition of field "key" incompatible with base class "A" +class D(A): + key: int # E: Definition of field "key" incompatible with base class "A" + +class XB: pass +class YB(X): pass +class ZB: pass +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testSelfRecursiveTypedDictInheriting] from typing import TypedDict @@ -2306,6 +2494,26 @@ v = {bad2: 2} # E: Missing key "num" for TypedDict "Value" \ [case testOperatorContainsNarrowsTypedDicts_unionWithList] from __future__ import annotations +from typing import assert_type, TypedDict, Union + +class D(TypedDict): + foo: int + + +d_or_list: D | list[str] + +if 'foo' in d_or_list: + assert_type(d_or_list, Union[D, list[str]]) +elif 'bar' in d_or_list: + assert_type(d_or_list, list[str]) +else: + assert_type(d_or_list, list[str]) + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_unionWithList_final] +from __future__ import annotations from typing import assert_type, final, TypedDict, Union @final @@ -2325,20 +2533,67 @@ else: [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] -[case testOperatorContainsNarrowsTypedDicts_total] +[case testOperatorContainsNarrowsTypedDicts_total_never] from __future__ import annotations from typing import assert_type, final, Literal, TypedDict, TypeVar, Union +from typing_extensions import Never, NotRequired -@final class D1(TypedDict): foo: int + bar: NotRequired[Never] + invalid: NotRequired[Never] + +class D2(TypedDict): + foo: NotRequired[Never] + bar: int + invalid: NotRequired[Never] + +d: D1 | D2 + +if 'foo' in d: + assert_type(d, D1) +else: + assert_type(d, D2) +foo_or_bar: Literal['foo', 'bar'] +if foo_or_bar in d: + assert_type(d, Union[D1, D2]) +else: + assert_type(d, Union[D1, D2]) + +foo_or_invalid: Literal['foo', 'invalid'] +if foo_or_invalid in d: + assert_type(d, D1) + # won't narrow 'foo_or_invalid' + assert_type(foo_or_invalid, Literal['foo', 'invalid']) +else: + assert_type(d, Union[D1, D2]) + # won't narrow 'foo_or_invalid' + assert_type(foo_or_invalid, Literal['foo', 'invalid']) + +TD = TypeVar('TD', D1, D2) + +def f(arg: TD) -> None: + value: int + if 'foo' in arg: + assert_type(arg['foo'], int) + else: + assert_type(arg['bar'], int) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_total_final] +from __future__ import annotations +from typing import assert_type, final, Literal, TypedDict, TypeVar, Union + +@final +class D1(TypedDict): + foo: int @final class D2(TypedDict): bar: int - d: D1 | D2 if 'foo' in d: @@ -2370,7 +2625,49 @@ def f(arg: TD) -> None: assert_type(arg['foo'], int) else: assert_type(arg['bar'], int) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_never] +# flags: --warn-unreachable +from __future__ import annotations +from typing import assert_type, TypedDict, Union +from typing_extensions import Never, NotRequired + +class DFooNotBar(TypedDict): + foo: int + bar: NotRequired[Never] + + +class DBar(TypedDict): + bar: int + + +d_bar: DBar + +if 'bar' in d_bar: + assert_type(d_bar, DBar) +else: + spam = 'ham' # E: Statement is unreachable + +if 'spam' in d_bar: + assert_type(d_bar, DBar) +else: + assert_type(d_bar, DBar) + +d_foo_not_bar: DFooNotBar +if 'spam' in d_foo_not_bar: + spam = 'ham' +else: + assert_type(d_foo_not_bar, DFooNotBar) + +d_union: DFooNotBar | DBar + +if 'foo' in d_union: + assert_type(d_union, Union[DFooNotBar, DBar]) +else: + assert_type(d_union, DBar) [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] @@ -2418,7 +2715,41 @@ else: [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] -[case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse] +[case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse_never] +from __future__ import annotations +from typing import assert_type, Literal, TypedDict, Union +from typing_extensions import Never, NotRequired + +class DTotal(TypedDict): + required_key: int + optional_key: NotRequired[Never] + +class DNotTotal(TypedDict, total=False): + required_key: Never + optional_key: int + +d: DTotal | DNotTotal + +if 'required_key' in d: + assert_type(d, DTotal) +else: + assert_type(d, DNotTotal) + +if 'optional_key' in d: + assert_type(d, DNotTotal) +else: + assert_type(d, Union[DTotal, DNotTotal]) + +key: Literal['optional_key', 'required_key'] +if key in d: + assert_type(d, Union[DTotal, DNotTotal]) +else: + assert_type(d, Union[DTotal, DNotTotal]) + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse_final] from __future__ import annotations from typing import assert_type, final, Literal, TypedDict, Union @@ -2426,12 +2757,10 @@ from typing import assert_type, final, Literal, TypedDict, Union class DTotal(TypedDict): required_key: int - @final class DNotTotal(TypedDict, total=False): optional_key: int - d: DTotal | DNotTotal if 'required_key' in d: @@ -2453,7 +2782,36 @@ else: [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] -[case testOperatorContainsNarrowsTypedDicts_partialThroughNotRequired] +[case testOperatorContainsNarrowsTypedDicts_partialThroughNotRequired_never] +from __future__ import annotations +from typing import assert_type, final, TypedDict, Union +from typing_extensions import Never, Required, NotRequired + +class D1(TypedDict): + required_key: Required[int] + optional_key: NotRequired[int] + +class D2(TypedDict): + abc: int + xyz: int + required_key: NotRequired[Never] + optional_key: NotRequired[Never] + +d: D1 | D2 + +if 'required_key' in d: + assert_type(d, D1) +else: + assert_type(d, D2) + +if 'optional_key' in d: + assert_type(d, D1) +else: + assert_type(d, Union[D1, D2]) + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] +[case testOperatorContainsNarrowsTypedDicts_partialThroughNotRequired_final] from __future__ import annotations from typing import assert_type, final, TypedDict, Union from typing_extensions import Required, NotRequired @@ -2463,13 +2821,11 @@ class D1(TypedDict): required_key: Required[int] optional_key: NotRequired[int] - @final class D2(TypedDict): abc: int xyz: int - d: D1 | D2 if 'required_key' in d: @@ -4011,6 +4367,43 @@ x["other"] = "a" # E: ReadOnly TypedDict key "other" TypedDict is mutated [typ [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testTypedDictFunctionalSyntaxWithMultipleSpecialForms] +from typing import ReadOnly, Required, NotRequired, TypedDict +from typing_extensions import Annotated +D1 = TypedDict("D1", {"x": int, "y": ReadOnly[Required[int]]}, total=False) +D2 = TypedDict("D2", {"x": int, "y": Required[ReadOnly[int]]}, total=False) +D3 = TypedDict("D3", {"x": int, "y": ReadOnly[NotRequired[int]]}) +D4 = TypedDict("D4", {"x": int, "y": NotRequired[ReadOnly[int]]}) +D5 = TypedDict("D5", {"x": int, "y": Annotated[ReadOnly[int], "some annotation text"]}) +D6 = TypedDict("D6", {"x": int, "y": ReadOnly[Annotated[int, "some annotation text"]]}) +D7 = TypedDict("D7", {"x": int, "y": Annotated[ReadOnly[NotRequired[int]], "some annotation text"]}) +D8 = TypedDict("D8", {"x": int, "y": NotRequired[ReadOnly[Annotated[int, "some annotation text"]]]}) +d1: D1 +d2: D2 +d3: D3 +d4: D4 +d5: D5 +d6: D6 +d7: D7 +d8: D8 +reveal_type(d1) # N: Revealed type is "TypedDict('__main__.D1', {'x'?: builtins.int, 'y'=: builtins.int})" +reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'x'?: builtins.int, 'y'=: builtins.int})" +reveal_type(d3) # N: Revealed type is "TypedDict('__main__.D3', {'x': builtins.int, 'y'?=: builtins.int})" +reveal_type(d4) # N: Revealed type is "TypedDict('__main__.D4', {'x': builtins.int, 'y'?=: builtins.int})" +reveal_type(d5) # N: Revealed type is "TypedDict('__main__.D5', {'x': builtins.int, 'y'=: builtins.int})" +reveal_type(d6) # N: Revealed type is "TypedDict('__main__.D6', {'x': builtins.int, 'y'=: builtins.int})" +reveal_type(d7) # N: Revealed type is "TypedDict('__main__.D7', {'x': builtins.int, 'y'?=: builtins.int})" +reveal_type(d8) # N: Revealed type is "TypedDict('__main__.D8', {'x': builtins.int, 'y'?=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictFunctionalSyntaxWithContradictorySpecialForms] +from typing import Required, NotRequired, TypedDict +D1 = TypedDict("D1", {"x": NotRequired[Required[int]]}) # E: "Required[]" type cannot be nested +D2 = TypedDict("D2", {"x": Required[NotRequired[int]]}) # E: "NotRequired[]" type cannot be nested +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictReadOnlyCreation] from typing import ReadOnly, TypedDict @@ -4227,10 +4620,236 @@ accepts_B(b) [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testTypedDictRequiredConsistentWithNotRequiredReadOnly] -from typing import NotRequired, ReadOnly, Required, TypedDict - -class A(TypedDict): +[case testTypedDictReadOnlyTypeVarBoundSubtyping] +from typing import ReadOnly, TypedDict, TypeVar +A = TypedDict('A', {'x': ReadOnly[int]}) +B = TypedDict('B', {'x': int}) +TA = TypeVar('TA', bound=A) +TB = TypeVar('TB', bound=B) +def fA(t: TA) -> TA: return t +def fB(t: TB) -> TB: return t +a: A +b: B +fA(a) +fA(b) +fB(a) # E: Value of type variable "TB" of "fB" cannot be "A" +fB(b) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlySubclassing] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[str] + +class B(A): + key: str + +a: A +b: B + +def accepts_A(d: A): ... +def accepts_B(d: B): ... + +accepts_A(a) +accepts_A(b) +accepts_B(a) # E: Argument 1 to "accepts_B" has incompatible type "A"; expected "B" +accepts_B(b) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlySubclassingFieldSubtype] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[float] +class B(A): + key: int +class C(A): + key: ReadOnly[int] + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'key'=: builtins.float})" +b: B +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'key': builtins.int})" +c: C +reveal_type(c) # N: Revealed type is "TypedDict('__main__.C', {'key'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlySubclassingFieldSubtypeWithForwardReferenceBases] +from typing import ReadOnly, TypedDict + +class X("XB"): pass +class Y("YB"): pass +class Z("ZB"): pass + +class A(TypedDict): + key: ReadOnly["X"] +class B(A): + key: "Y" # ok: Y subclasses X via YB +class C(A): + key: "Z" # E: Definition of field "key" incompatible with base class "A" + +x: A +y: B +x = y # ok: B is a valid subtype of A + +class XB: pass +class YB(X): pass +class ZB: pass +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlyIllegalSubclassingFieldSupertype] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[int] +class B(A): + key: float # E: Definition of field "key" incompatible with base class "A" +class C(A): + key: ReadOnly[float] # E: Definition of field "key" incompatible with base class "A" + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'key'=: builtins.int})" +b: B +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'key': builtins.float})" +c: C +reveal_type(c) # N: Revealed type is "TypedDict('__main__.C', {'key'=: builtins.float})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlyMixinFieldSubtype] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[float] + +class B(TypedDict): + key: ReadOnly[int] + +class C(B, A): # Left-most base "wins" + pass + +class D(A, B): # E: Incompatible definitions of field "key" in base classes "B" and "A" \ + # N: This can be resolved by redeclaring the field "key" with a mutually compatible type + pass + +class E(A, B): + key: ReadOnly[int] # ok: explicit redefinition + +c: C +reveal_type(c) # N: Revealed type is "TypedDict('__main__.C', {'key'=: builtins.int})" +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'key'=: builtins.float})" +e: E +reveal_type(e) # N: Revealed type is "TypedDict('__main__.E', {'key'=: builtins.int})" + +# Verify that subclasses are valid subtypes +a: A +b: B +a = c +b = c +a = e +b = e +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlySubclassingTotality] +from typing import ReadOnly, TypedDict + +class A(TypedDict, total=False): + key: ReadOnly[int] + +class B(A): + key: int + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'key'?=: builtins.int})" +b: B +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'key': builtins.int})" + +a = b # ok: B is a valid subtype of A +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlyIllegalSubclassingTotality] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[int] + +class B(A, total=False): + key: int # E: Field "key" is required in base class "A" + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'key'=: builtins.int})" +b: B +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'key'?: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlyMixinTotality] +from typing import ReadOnly, TypedDict + +class A(TypedDict, total=True): + key: ReadOnly[int] + +class B(TypedDict, total=False): + key: ReadOnly[int] + +class C(B, A): + pass +class D(A, B): + pass + +c: C +reveal_type(c) # N: Revealed type is "TypedDict('__main__.C', {'key'=: builtins.int})" +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'key'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictReadOnlyMixinWithMutableField] +from typing import ReadOnly, TypedDict + +class A(TypedDict): + key: ReadOnly[float] + +class B(TypedDict): + key: float + +class C(TypedDict): + key: int + +class D1(A, B): pass +d1: D1 +reveal_type(d1) # N: Revealed type is "TypedDict('__main__.D1', {'key': builtins.float})" +class D2(B, A): pass +d2: D2 +reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'key': builtins.float})" +class D3(A, C): pass +d3: D3 +reveal_type(d3) # N: Revealed type is "TypedDict('__main__.D3', {'key': builtins.int})" +class D4(C, A): pass +d4: D4 +reveal_type(d4) # N: Revealed type is "TypedDict('__main__.D4', {'key': builtins.int})" +class D5(A, B, C): pass # E: Incompatible definitions of field "key" in base classes "C" and "B" +d5: D5 +reveal_type(d5) # N: Revealed type is "TypedDict('__main__.D5', {'key': builtins.float})" +class D6(C, B, A): pass # E: Incompatible definitions of field "key" in base classes "B" and "C" +d6: D6 +reveal_type(d6) # N: Revealed type is "TypedDict('__main__.D6', {'key': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + + +[case testTypedDictRequiredConsistentWithNotRequiredReadOnly] +from typing import NotRequired, ReadOnly, Required, TypedDict + +class A(TypedDict): x: NotRequired[ReadOnly[str]] class B(TypedDict): @@ -4241,6 +4860,20 @@ def f(b: B): [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testTypedDictNotRequiredInconsistentWithRequiredReadOnly] +from typing import NotRequired, ReadOnly, Required, TypedDict + +class A(TypedDict): + x: Required[ReadOnly[str]] + +class B(TypedDict): + x: NotRequired[str] + +def f(b: B): + a: A = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictReadOnlyCall] from typing import ReadOnly, TypedDict @@ -4288,7 +4921,7 @@ x["two"] = "a" # E: ReadOnly TypedDict key "two" TypedDict is mutated [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testMeetOfTypedDictsWithReadOnly] +[case testMeetOfTypedDictsWithNonOverlappingReadOnlyKeys] from typing import TypeVar, Callable, TypedDict, ReadOnly XY = TypedDict('XY', {'x': ReadOnly[int], 'y': int}) YZ = TypedDict('YZ', {'y': int, 'z': ReadOnly[int]}) @@ -4299,6 +4932,160 @@ reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'=: builtins.int, 'y': bu [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testMeetOfTypedDictsWithOverlappingReadOnlyAndMutableKeys] +from typing import TypedDict, TypeVar, Callable, ReadOnly +XY = TypedDict('XY', {'x': int, 'y': int}) +YZ = TypedDict('YZ', {'y': ReadOnly[int], 'z': int}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: XY, y: YZ) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': builtins.int, 'z': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithDifferingMutabilityAndAny] +from typing import Any, TypedDict, TypeVar, Callable, ReadOnly +A = TypedDict('A', {'x': Any}) +B = TypedDict('B', {'x': ReadOnly[int]}) +C = TypedDict('C', {'x': ReadOnly[Any]}) +D = TypedDict('D', {'x': int}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def gAB(x: A, y: B) -> None: pass +def gBA(x: B, y: A) -> None: pass +def gCD(x: C, y: D) -> None: pass +def gDC(x: D, y: C) -> None: pass +reveal_type(f(gAB)) # N: Revealed type is "TypedDict({'x': builtins.int})" +reveal_type(f(gBA)) # N: Revealed type is "TypedDict({'x': builtins.int})" +reveal_type(f(gCD)) # N: Revealed type is "TypedDict({'x': builtins.int})" +reveal_type(f(gDC)) # N: Revealed type is "TypedDict({'x': builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithIncompatibleReadOnlyCommonKeysIsUninhabitedUnlessNotRequired] +from typing import TypedDict, TypeVar, Callable, ReadOnly +A = TypedDict('A', {'x': ReadOnly[int]}) +B = TypedDict('B', {'x': ReadOnly[str]}) +C = TypedDict('C', {'x': ReadOnly[int]}, total=False) +D = TypedDict('D', {'x': ReadOnly[str]}, total=False) +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +def fAB(x: A, y: B) -> None: pass +def fAD(x: A, y: D) -> None: pass +def fCB(x: C, y: B) -> None: pass +def fCD(x: C, y: D) -> None: pass +if int(): + reveal_type(meet(fAB)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fAD)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fCB)) # N: Revealed type is "Never" +reveal_type(meet(fCD)) # N: Revealed type is "TypedDict({'x'?=: Never})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithCompatibleReadOnlyAndMutableCommonKeys] +from typing import TypedDict, TypeVar, Callable, NotRequired, ReadOnly, Union +A = TypedDict('A', {'x': int, 'y': ReadOnly[Union[int, str]]}) +B = TypedDict('B', {'x': ReadOnly[float], 'y': str}) +C = TypedDict('C', {'x': ReadOnly[NotRequired[float]], 'y': str}) +D = TypedDict('D', {'x': NotRequired[int], 'y': ReadOnly[Union[int, str]]}) +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +def fAB(x: A, y: B) -> None: pass +def fBA(x: B, y: A) -> None: pass +def fAC(x: A, y: C) -> None: pass +def fCA(x: C, y: A) -> None: pass +def fCD(x: C, y: D) -> None: pass +def fDC(x: D, y: C) -> None: pass +reveal_type(meet(fAB)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': builtins.str})" +reveal_type(meet(fBA)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': builtins.str})" +reveal_type(meet(fAC)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': builtins.str})" +reveal_type(meet(fCA)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y': builtins.str})" +reveal_type(meet(fCD)) # N: Revealed type is "TypedDict({'x'?: builtins.int, 'y': builtins.str})" +reveal_type(meet(fDC)) # N: Revealed type is "TypedDict({'x'?: builtins.int, 'y': builtins.str})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithIncompatibleReadOnlyAndMutableCommonKeysIsUninhabited] +from typing import TypedDict, TypeVar, Callable, ReadOnly +XYa = TypedDict('XYa', {'x': int, 'y': ReadOnly[int]}) +YbZ = TypedDict('YbZ', {'y': str, 'z': int}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: XYa, y: YbZ) -> None: pass +reveal_type(f(g)) # N: Revealed type is "Never" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithCompatibleReadOnlyCommonKeys] +from typing import TypedDict, TypeVar, Callable, ReadOnly +A = TypedDict('A', {'a': int}) +B = TypedDict('B', {'b': int}) +Xa = TypedDict('Xa', {'x': ReadOnly[A]}) +Xb = TypedDict('Xb', {'x': ReadOnly[B]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: Xa, y: Xb) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'=: TypedDict({'a': builtins.int, 'b': builtins.int})})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsWithReadOnlyKeysAndAny] +from typing import Any, TypedDict, TypeVar, Callable, ReadOnly +A = TypedDict('A', {'x': ReadOnly[Any]}) +B = TypedDict('B', {'x': ReadOnly[int]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: A, y: B) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfReadOnlyTypedDictsWithNoCommonKeysHasAllKeysAndNewFallback] +from typing import TypedDict, TypeVar, Callable, ReadOnly +X = TypedDict('X', {'x': ReadOnly[int]}) +Z = TypedDict('Z', {'z': ReadOnly[int]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: X, y: Z) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'=: builtins.int, 'z'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfReadOnlyTypedDictsWithNonTotal] +from typing import TypedDict, TypeVar, Callable, ReadOnly +XY = TypedDict('XY', {'x': ReadOnly[int], 'y': ReadOnly[int]}, total=False) +YZ = TypedDict('YZ', {'y': ReadOnly[int], 'z': ReadOnly[int]}, total=False) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: XY, y: YZ) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'?=: builtins.int, 'y'?=: builtins.int, 'z'?=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfReadOnlyTypedDictsWithNonOverlappingNonTotalAndTotal] +from typing import TypedDict, TypeVar, Callable, ReadOnly +XY = TypedDict('XY', {'x': ReadOnly[int]}, total=False) +YZ = TypedDict('YZ', {'y': ReadOnly[int], 'z': ReadOnly[int]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: XY, y: YZ) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'?=: builtins.int, 'y'=: builtins.int, 'z'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfReadOnlyTypedDictsWithOverlappingNonTotalAndTotal] +from typing import TypedDict, TypeVar, Callable, ReadOnly +XY = TypedDict('XY', {'x': ReadOnly[int], 'y': ReadOnly[int]}, total=False) +YZ = TypedDict('YZ', {'y': ReadOnly[int], 'z': ReadOnly[int]}) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: XY, y: YZ) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({'x'?=: builtins.int, 'y'=: builtins.int, 'z'=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictReadOnlyUnpack] from typing import TypedDict from typing_extensions import Unpack, ReadOnly @@ -4388,6 +5175,725 @@ c: C = d # E: Incompatible types in assignment (expression has type "D", variab [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testTypedDictJoinWithReadOnly] +from typing import ReadOnly, TypedDict, TypeVar +A = TypedDict('A', {'x': ReadOnly[int]}) +B = TypedDict('B', {'x': ReadOnly[int]}, total=False) +C = TypedDict('C', {'x': int}) +D = TypedDict('D', {'x': int}, total=False) +T = TypeVar('T') +def j(x: T, y: T) -> T: return x +a: A +b: B +c: C +d: D +reveal_type(j(a, b)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +reveal_type(j(b, a)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +reveal_type(j(a, c)) # N: Revealed type is "TypedDict({'x'=: builtins.int})" +reveal_type(j(c, a)) # N: Revealed type is "TypedDict({'x'=: builtins.int})" +reveal_type(j(a, d)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +reveal_type(j(d, a)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +reveal_type(j(b, c)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +reveal_type(j(c, b)) # N: Revealed type is "TypedDict({'x'?=: builtins.int})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + + +# Closed +# See https://peps.python.org/pep-0728/ + +[case testTypedDictWithClosedFalse] +from typing import TypedDict +D = TypedDict('D', {'x': int, 'y': str}, closed=False) +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int, 'y': builtins.str})" +class E(TypedDict, closed=False): + x: int + y: str +e: E +reveal_type(e) # N: Revealed type is "TypedDict('__main__.E', {'x': builtins.int, 'y': builtins.str})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithClosedTrue] +from typing import TypedDict +D = TypedDict('D', {'x': int, 'y': str}, closed=True) +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int, 'y': builtins.str}, closed=True)" +class E(TypedDict, closed=True): + x: int + y: str +e: E +reveal_type(e) # N: Revealed type is "TypedDict('__main__.E', {'x': builtins.int, 'y': builtins.str}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithInvalidClosedArgument] +from typing import TypedDict +A = TypedDict('A', {'x': int}, closed=0) # E: "closed" argument must be a True or False literal +B = TypedDict('B', {'x': int}, closed=bool) # E: "closed" argument must be a True or False literal +class C(TypedDict, closed=0): # E: "closed" argument must be a True or False literal + x: int +class D(TypedDict, closed=bool): # E: "closed" argument must be a True or False literal + x: int +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithClosedAndTotal] +from typing import TypedDict +A = TypedDict('A', {'x': int, 'y': str}, total=False, closed=True) +B = TypedDict('B', {'x': int, 'y': str}, closed=True, total=False) +C = TypedDict('C', {'x': int, 'y': str}, total=True, closed=False) +a: A +b: B +c: C +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'x'?: builtins.int, 'y'?: builtins.str}, closed=True)" +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'x'?: builtins.int, 'y'?: builtins.str}, closed=True)" +reveal_type(c) # N: Revealed type is "TypedDict('__main__.C', {'x': builtins.int, 'y': builtins.str})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithDuplicateKeywordArguments_no_parallel] +from typing import TypedDict +A = TypedDict('A', {'x': int}, closed=True, closed=False) # E: Repeated keyword argument "closed" for "TypedDict" +B = TypedDict('B', {'x': int}, total=True, total=False) # E: Repeated keyword argument "total" for "TypedDict" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubclassingClosed] +from typing import TypedDict +class D(TypedDict, closed=True): + x: int + y: float +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int, 'y': builtins.float}, closed=True)" + +class D1(D): pass +d1: D1 +reveal_type(d1) # N: Revealed type is "TypedDict('__main__.D1', {'x': builtins.int, 'y': builtins.float}, closed=True)" + +class D2(D, closed=True): pass +d2: D2 +reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'x': builtins.int, 'y': builtins.float}, closed=True)" + +class D3(D, closed=False): pass # E: Open TypedDict class cannot subclass closed TypedDict class "D" +d3: D3 +reveal_type(d3) # N: Revealed type is "TypedDict('__main__.D3', {'x': builtins.int, 'y': builtins.float})" + +class D4(D): + z: int # E: Cannot extend closed base class "D" with new field "z" +d4: D4 +reveal_type(d4) # N: Revealed type is "TypedDict('__main__.D4', {'x': builtins.int, 'y': builtins.float, 'z': builtins.int}, closed=True)" + +class D5(D): + x: int +d5: D5 +reveal_type(d5) # N: Revealed type is "TypedDict('__main__.D5', {'x': builtins.int, 'y': builtins.float}, closed=True)" + +class D6(D): + x: float # E: Definition of field "x" incompatible with base class "D" + y: int # E: Definition of field "y" incompatible with base class "D" +d6: D6 +reveal_type(d6) # N: Revealed type is "TypedDict('__main__.D6', {'x': builtins.float, 'y': builtins.int}, closed=True)" + +class D7(TypedDict, closed=True): + x: int + z: float +class D8(D7, D): # E: Cannot extend closed base class "D7" with field "y" from base class "D" \ + # E: Cannot extend closed base class "D" with field "z" from base class "D7" + pass +d8: D8 +reveal_type(d8) # N: Revealed type is "TypedDict('__main__.D8', {'x': builtins.int, 'y': builtins.float, 'z': builtins.float}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubclassingOpenAndClosed] +from typing import TypedDict +class B1(TypedDict, closed=True): + x: int + y: float +class B2(TypedDict): + x: int +class D1(B1, B2): pass +class D2(B2, B1): pass +d1: D1 +d2: D2 +reveal_type(d1) # N: Revealed type is "TypedDict('__main__.D1', {'x': builtins.int, 'y': builtins.float}, closed=True)" +reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'x': builtins.int, 'y': builtins.float}, closed=True)" +class B3(TypedDict): + z: int +class D3(B1, B3): pass # E: Cannot extend closed base class "B1" with field "z" from base class "B3" +class D4(B3, B1): pass # E: Cannot extend closed base class "B1" with field "z" from base class "B3" +d3: D3 +d4: D4 +reveal_type(d3) # N: Revealed type is "TypedDict('__main__.D3', {'z': builtins.int, 'x': builtins.int, 'y': builtins.float}, closed=True)" +reveal_type(d4) # N: Revealed type is "TypedDict('__main__.D4', {'x': builtins.int, 'y': builtins.float, 'z': builtins.int}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testCanSubclassClosedTypedDictWithForwardDeclarations] +from typing import TypedDict, final + +class D1(TypedDict, closed=True): + forward_declared: "ForwardDeclared" + +class D2(D1): + pass + +class ForwardDeclared: pass + +d2: D2 +reveal_type(d2) # N: Revealed type is "TypedDict('__main__.D2', {'forward_declared': __main__.ForwardDeclared}, closed=True)" + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testTypedDictSubtypingClosedMustRemainClosed] +from typing import TypedDict +A = TypedDict('A', {'x': int}, closed=True) +B = TypedDict('B', {'x': int}, closed=True) +C = TypedDict('C', {'x': int}, closed=False) +D = TypedDict('D', {'x': int}, closed=False) +def f(x: A) -> None: pass +def g(x: C) -> None: pass +a: A +b: B +c: C +d: D +f(b) +f(d) # E: Argument 1 to "f" has incompatible type "D"; expected "A" +g(d) +g(b) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubtypingCannotAddKeysToAClosedSupertype] +from typing import TypedDict +A = TypedDict('A', {'x': int}, closed=True) +B = TypedDict('B', {'x': int, 'y': int}, closed=True) +C = TypedDict('C', {'x': int}) +D = TypedDict('D', {'x': int, 'y': int}) +def f(x: A) -> None: pass +def g(x: C) -> None: pass +b: B +d: D +f(b) # E: Argument 1 to "f" has incompatible type "B"; expected "A" +g(d) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubtypingCanDropOptionalReadonlyKeysInAClosedSubtype] +from typing import ReadOnly, TypedDict +# Optional, readonly, closed: permitted +A = TypedDict('A', {'x': int, 'y': ReadOnly[int]}, total=False, closed=True) +B = TypedDict('B', {'x': int}, total=False, closed=True) +b: B +def f_a(x: A) -> None: pass +f_a(b) + +# Optional, readonly, open: not permitted +C = TypedDict('C', {'x': int, 'y': ReadOnly[int]}, total=False) +D = TypedDict('D', {'x': int}, total=False) +d: D +def f_c(x: C) -> None: pass +f_c(d) # E: Argument 1 to "f_c" has incompatible type "D"; expected "C" + +# Optional, mutable, closed: not permitted +E = TypedDict('E', {'x': int, 'y': int}, total=False, closed=True) +F = TypedDict('F', {'x': int}, total=False, closed=True) +f: F +def f_e(x: E) -> None: pass +f_e(f) # E: Argument 1 to "f_e" has incompatible type "F"; expected "E" + +# Required, readonly, closed: not permitted +G = TypedDict('G', {'x': int, 'y': ReadOnly[int]}, closed=True) +H = TypedDict('H', {'x': int}, closed=True) +h: H +def f_g(x: G) -> None: pass +f_g(h) # E: Argument 1 to "f_g" has incompatible type "H"; expected "G" + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubtypingClosedTypeVarBound] +from typing import TypedDict, TypeVar +A = TypedDict('A', {'x': int}, closed=True) +B = TypedDict('B', {'x': int}) +TA = TypeVar('TA', bound=A) +TB = TypeVar('TB', bound=B) +def fA(t: TA) -> TA: return t +def fB(t: TB) -> TB: return t +a: A +b: B +fA(a) +fA(b) # E: Value of type variable "TA" of "fA" cannot be "B" +fB(a) +fB(b) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictSubtypingClosedExplicitNever] +from typing import NotRequired, ReadOnly, TypedDict +from typing_extensions import Never +A = TypedDict('A', {}, closed=True) +B = TypedDict('B', {'x': NotRequired[Never]}, closed=True) +C = TypedDict('C', {'x': ReadOnly[NotRequired[Never]]}, closed=True) +def f_a(x: A) -> None: pass +def f_b(x: B) -> None: pass +def f_c(x: C) -> None: pass +a: A +b: B +c: C +# Logically identical types: legal +f_a(b) +f_b(a) +# Subtype allows key removal: legal +f_c(a) +f_c(b) +# Subtype prevents key removal: illegal +f_a(c) # E: Argument 1 to "f_a" has incompatible type "C"; expected "A" +f_b(c) # E: Argument 1 to "f_b" has incompatible type "C"; expected "B" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testJoinOfClosedTypedDict] +from typing import Any, TypedDict, TypeVar +A = TypedDict('A', {'x': int}, closed=True) +B = TypedDict('B', {'y': int}) +C = TypedDict('C', {'y': int}, closed=True) +D = TypedDict('D', {'x': int, 'y': int}, closed=True) +E = TypedDict('E', {'x': Any, 'y': Any}) +T = TypeVar('T') +def j(x: T, y: T) -> T: return x +a: A +b: B +c: C +d: D +e: E +reveal_type(j(a, b)) # N: Revealed type is "TypedDict({'y'?=: builtins.int})" +reveal_type(j(b, a)) # N: Revealed type is "TypedDict({'y'?=: builtins.int})" +reveal_type(j(a, c)) # N: Revealed type is "TypedDict({'x'?=: builtins.int, 'y'?=: builtins.int}, closed=True)" +reveal_type(j(c, a)) # N: Revealed type is "TypedDict({'y'?=: builtins.int, 'x'?=: builtins.int}, closed=True)" +reveal_type(j(a, d)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y'?=: builtins.int}, closed=True)" +reveal_type(j(d, a)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y'?=: builtins.int}, closed=True)" +reveal_type(j(a, e)) # N: Revealed type is "TypedDict({'x': Any, 'y'?=: Any})" +reveal_type(j(e, a)) # N: Revealed type is "TypedDict({'x': Any, 'y'?=: Any})" +reveal_type(j(b, e)) # N: Revealed type is "TypedDict({'y': Any})" +reveal_type(j(e, b)) # N: Revealed type is "TypedDict({'y': Any})" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfClosedTypedDictsWithMatchingRequiredKeysIsNotAnonymous] +from typing import TypedDict, TypeVar, Callable +X = TypedDict('X', {'x': int, 'y': int}, closed=True) +Y = TypedDict('Y', {'x': int, 'y': int}, closed=True) +Z = TypedDict('Z', {'x': int, 'y': int}) +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +def fXY(x: X, y: Y) -> None: pass +def fYX(x: Y, y: X) -> None: pass +def fXZ(x: X, y: Z) -> None: pass +def fZX(x: Z, y: X) -> None: pass +reveal_type(meet(fXY)) # N: Revealed type is "TypedDict('__main__.X', {'x': builtins.int, 'y': builtins.int}, closed=True)" +reveal_type(meet(fYX)) # N: Revealed type is "TypedDict('__main__.Y', {'x': builtins.int, 'y': builtins.int}, closed=True)" +reveal_type(meet(fXZ)) # N: Revealed type is "TypedDict('__main__.X', {'x': builtins.int, 'y': builtins.int}, closed=True)" +reveal_type(meet(fZX)) # N: Revealed type is "TypedDict('__main__.X', {'x': builtins.int, 'y': builtins.int}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfClosedTypedDictsWithDifferentKeys] +from typing import TypedDict, TypeVar, Callable, ReadOnly +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +A = TypedDict('A', {'a': int}, closed=True) +B = TypedDict('B', {'b': int}, closed=True) +def fAB(x: A, y: B) -> None: pass +if int(): + reveal_type(meet(fAB)) # N: Revealed type is "Never" +C = TypedDict('C', {'c': int}, closed=True, total=False) +D = TypedDict('D', {'d': int}, closed=True, total=False) +def fCD(x: C, y: D) -> None: pass +if int(): + reveal_type(meet(fCD)) # N: Revealed type is "Never" +E = TypedDict('E', {'e': ReadOnly[int]}, closed=True) +F = TypedDict('F', {'f': ReadOnly[int]}, closed=True) +def fEF(x: E, y: F) -> None: pass +if int(): + reveal_type(meet(fEF)) # N: Revealed type is "Never" +G = TypedDict('G', {'g': ReadOnly[int]}, closed=True, total=False) +H = TypedDict('H', {'h': ReadOnly[int]}, closed=True, total=False) +def fAH(x: A, y: H) -> None: pass +if int(): + reveal_type(meet(fAH)) # N: Revealed type is "Never" +def fCH(x: C, y: H) -> None: pass +if int(): + reveal_type(meet(fCH)) # N: Revealed type is "Never" +def fEH(x: E, y: H) -> None: pass +if int(): + reveal_type(meet(fEH)) # N: Revealed type is "Never" +def fGH(x: G, y: H) -> None: pass +if int(): + reveal_type(meet(fGH)) # N: Revealed type is "TypedDict({}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsExplicitAndImplicitClosedMissing] +from typing import Any, NotRequired, ReadOnly, TypedDict, TypeVar, Callable +from typing_extensions import Never +A = TypedDict('A', {'x': ReadOnly[NotRequired[Never]]}, closed=True) +B = TypedDict('B', {'y': ReadOnly[NotRequired[Never]]}, closed=True) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: A, y: B) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictClosedMissingAndAny] +from typing import Any, TypedDict, TypeVar, Callable, ReadOnly +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +A = TypedDict('A', {}, closed=True) +B = TypedDict('B', {'x': Any}) +C = TypedDict('C', {'x': Any}, total=False) +def fAB(x: A, y: B) -> None: pass +def fBA(x: B, y: A) -> None: pass +def fAC(x: A, y: C) -> None: pass +def fCA(x: C, y: A) -> None: pass +if int(): + reveal_type(meet(fAB)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fBA)) # N: Revealed type is "Never" +reveal_type(meet(fAC)) # N: Revealed type is "TypedDict({}, closed=True)" +reveal_type(meet(fCA)) # N: Revealed type is "TypedDict({}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfTypedDictsClosedNotRequiredNeverAndAny] +from typing import Any, NotRequired, TypedDict, TypeVar, Callable +from typing_extensions import Never +A = TypedDict('A', {'x': NotRequired[Any], 'y': NotRequired[Never]}, closed=True) +B = TypedDict('B', {'x': NotRequired[Never], 'y': NotRequired[Any]}, closed=True) +T = TypeVar('T') +def f(x: Callable[[T, T], None]) -> T: pass +def g(x: A, y: B) -> None: pass +reveal_type(f(g)) # N: Revealed type is "TypedDict({}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfOpenAndClosedTypedDictsExtraKeyInClosed] +from typing import TypedDict, TypeVar, Callable, ReadOnly +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +Ax = TypedDict('Ax', {'a': ReadOnly[float]}) +AyB = TypedDict('AyB', {'a': ReadOnly[int], 'b': int}, closed=True) +def fAxAyB(x: Ax, y: AyB) -> None: pass +def fAyBAx(x: AyB, y: Ax) -> None: pass +reveal_type(meet(fAxAyB)) # N: Revealed type is "TypedDict({'a'=: builtins.int, 'b': builtins.int}, closed=True)" +reveal_type(meet(fAyBAx)) # N: Revealed type is "TypedDict({'a'=: builtins.int, 'b': builtins.int}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testMeetOfOpenAndClosedTypedDictsExtraKeyInOpen] +from typing import TypedDict, TypeVar, Callable, ReadOnly +T = TypeVar('T') +def meet(x: Callable[[T, T], None]) -> T: pass +C = TypedDict('C', {'a': ReadOnly[float]}, closed=True) +D1 = TypedDict('D1', {'a': ReadOnly[int], 'b': int}) +D2 = TypedDict('D2', {'a': ReadOnly[int], 'b': ReadOnly[int]}) +D3 = TypedDict('D3', {'a': ReadOnly[int], 'b': int}, total=False) +D4 = TypedDict('D4', {'a': ReadOnly[int], 'b': ReadOnly[int]}, total=False) +def fCD1(x: C, y: D1) -> None: pass +def fD1C(x: D1, y: C) -> None: pass +def fCD2(x: C, y: D2) -> None: pass +def fD2C(x: D2, y: C) -> None: pass +def fCD3(x: C, y: D3) -> None: pass +def fD3C(x: D3, y: C) -> None: pass +def fCD4(x: C, y: D4) -> None: pass +def fD4C(x: D4, y: C) -> None: pass +if int(): + reveal_type(meet(fCD1)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fD1C)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fCD2)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fD2C)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fCD3)) # N: Revealed type is "Never" +if int(): + reveal_type(meet(fD3C)) # N: Revealed type is "Never" +reveal_type(meet(fCD4)) # N: Revealed type is "TypedDict({'a'=: builtins.int}, closed=True)" +reveal_type(meet(fD4C)) # N: Revealed type is "TypedDict({'a'=: builtins.int}, closed=True)" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictGetMethodClosed] +from typing import TypedDict, Literal +class Unrelated: pass +D = TypedDict('D', {'x': int, 'y': str}, closed=True) +d: D +u: Unrelated +x: Literal['x'] +y: Literal['y'] +z: Literal['z'] +x_or_y: Literal['x', 'y'] +x_or_z: Literal['x', 'z'] +x_or_y_or_z: Literal['x', 'y', 'z'] + +# test with literal expression +reveal_type(d.get('x')) # N: Revealed type is "builtins.int" +reveal_type(d.get('y')) # N: Revealed type is "builtins.str" +reveal_type(d.get('z')) # N: Revealed type is "None" +reveal_type(d.get('z', u)) # N: Revealed type is "__main__.Unrelated" + +# test with literal type / union of literal types with implicit default +reveal_type(d.get(x)) # N: Revealed type is "builtins.int" +reveal_type(d.get(y)) # N: Revealed type is "builtins.str" +reveal_type(d.get(z)) # N: Revealed type is "None" +reveal_type(d.get(x_or_y)) # N: Revealed type is "builtins.int | builtins.str" +reveal_type(d.get(x_or_z)) # N: Revealed type is "builtins.int | None" +reveal_type(d.get(x_or_y_or_z)) # N: Revealed type is "builtins.int | builtins.str | None" + +# test with literal type / union of literal types with explicit default +reveal_type(d.get(x, u)) # N: Revealed type is "builtins.int" +reveal_type(d.get(y, u)) # N: Revealed type is "builtins.str" +reveal_type(d.get(z, u)) # N: Revealed type is "__main__.Unrelated" +reveal_type(d.get(x_or_y, u)) # N: Revealed type is "builtins.int | builtins.str" +reveal_type(d.get(x_or_z, u)) # N: Revealed type is "builtins.int | __main__.Unrelated" +reveal_type(d.get(x_or_y_or_z, u)) # N: Revealed type is "builtins.int | builtins.str | __main__.Unrelated" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testOperatorContainsNarrowsTypedDicts_unionWithList_closed] +from __future__ import annotations +from typing import assert_type, TypedDict, Union + +class D(TypedDict, closed=True): + foo: int + +d_or_list: D | list[str] + +if 'foo' in d_or_list: + assert_type(d_or_list, Union[D, list[str]]) +elif 'bar' in d_or_list: + assert_type(d_or_list, list[str]) +else: + assert_type(d_or_list, list[str]) + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_total_closed] +from __future__ import annotations +from typing import assert_type, Literal, TypedDict, TypeVar, Union + +class D1(TypedDict, closed=True): + foo: int + +class D2(TypedDict, closed=True): + bar: int + +d: D1 | D2 + +if 'foo' in d: + assert_type(d, D1) +else: + assert_type(d, D2) + +foo_or_bar: Literal['foo', 'bar'] +if foo_or_bar in d: + assert_type(d, Union[D1, D2]) +else: + assert_type(d, Union[D1, D2]) + +foo_or_invalid: Literal['foo', 'invalid'] +if foo_or_invalid in d: + assert_type(d, D1) + # won't narrow 'foo_or_invalid' + assert_type(foo_or_invalid, Literal['foo', 'invalid']) +else: + assert_type(d, Union[D1, D2]) + # won't narrow 'foo_or_invalid' + assert_type(foo_or_invalid, Literal['foo', 'invalid']) + +TD = TypeVar('TD', D1, D2) + +def f(arg: TD) -> None: + value: int + if 'foo' in arg: + assert_type(arg['foo'], int) + else: + assert_type(arg['bar'], int) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_closed] +# flags: --warn-unreachable +from __future__ import annotations +from typing import assert_type, TypedDict, Union + +class DClosed(TypedDict, closed=True): + foo: int + +class DNotClosed(TypedDict): + bar: int + +d_not_closed: DNotClosed + +if 'bar' in d_not_closed: + assert_type(d_not_closed, DNotClosed) +else: + spam = 'ham' # E: Statement is unreachable + +if 'spam' in d_not_closed: + assert_type(d_not_closed, DNotClosed) +else: + assert_type(d_not_closed, DNotClosed) + +d_closed: DClosed + +if 'spam' in d_closed: + spam = 'ham' # E: Statement is unreachable +else: + assert_type(d_closed, DClosed) + +d_union: DClosed | DNotClosed + +if 'foo' in d_union: + assert_type(d_union, Union[DClosed, DNotClosed]) +else: + assert_type(d_union, DNotClosed) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse_closed] +from __future__ import annotations +from typing import assert_type, Literal, TypedDict, Union + +class DTotal(TypedDict, closed=True): + required_key: int + +class DNotTotal(TypedDict, total=False, closed=True): + optional_key: int + +d: DTotal | DNotTotal + +if 'required_key' in d: + assert_type(d, DTotal) +else: + assert_type(d, DNotTotal) + +if 'optional_key' in d: + assert_type(d, DNotTotal) +else: + assert_type(d, Union[DTotal, DNotTotal]) + +key: Literal['optional_key', 'required_key'] +if key in d: + assert_type(d, Union[DTotal, DNotTotal]) +else: + assert_type(d, Union[DTotal, DNotTotal]) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypedDicts_partialThroughNotRequired_closed] +from __future__ import annotations +from typing import assert_type, TypedDict, Union +from typing_extensions import Required, NotRequired + +class D1(TypedDict, closed=True): + required_key: Required[int] + optional_key: NotRequired[int] + +class D2(TypedDict, closed=True): + abc: int + xyz: int + +d: D1 | D2 + +if 'required_key' in d: + assert_type(d, D1) +else: + assert_type(d, D2) + +if 'optional_key' in d: + assert_type(d, D1) +else: + assert_type(d, Union[D1, D2]) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] + +[case testOperatorContainsNarrowsTypeVarWithClosedTypedDictBound] +from typing import TypedDict, TypeVar +from typing_extensions import Never, assert_type + +T = TypeVar('T', bound='D') + +class D(TypedDict, closed=True): + a: int + +def func(t: T): + if "b" in t: + assert_type(t, Never) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackFromClosedMissingKey] +# flags: --extra-checks +from typing import TypedDict +from typing_extensions import Never, NotRequired +D1 = TypedDict("D1", {"a": int, "b": str}, closed=True) +D2 = TypedDict("D2", {"a": int, "b": str, "c": int}) +D3 = TypedDict("D3", {"a": int, "b": str, "c": NotRequired[int]}) +d1: D1 +d2: D2 = {**d1} # E: Missing key "c" for TypedDict "D2" +d3: D3 = {**d1} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + +[case testTypedDictUnpackIntoClosed] +# flags: --extra-checks +from typing import Any, Mapping, TypedDict, Union +from typing_extensions import Never, NotRequired +D1 = TypedDict("D1", {"a": int, "b": str}, closed=True) +D2 = TypedDict("D2", {"a": int, "b": str}) +D3 = TypedDict("D3", {"c": int, "d": str}, closed=True) +D4 = TypedDict("D4", {"c": int, "d": str}) +D5 = TypedDict("D5", {"a": int, "b": str, "c": int, "d": str}, closed=True) +d1: D1 +d2: D2 +d3: D3 +d4: D4 +d5: D5 +m: Mapping[Any, Any] +u34: Union[D3, D4] +u35: Union[D3, D5] +u5m: Union[D5, Mapping[Any, Any]] +d5 = {**d1, **d3} +d5 = { + **d1, + **d4, # E: Cannot unpack item that may contain extra keys into a closed TypedDict +} +d5 = { + **d2, # E: Cannot unpack item that may contain extra keys into a closed TypedDict + **d3, +} +d5 = { + **d1, + **u34, # E: Cannot unpack item that may contain extra keys into a closed TypedDict +} +d5 = {**d1, **u35} +d5 = { + **m, # E: Cannot unpack item that may contain extra keys into a closed TypedDict + **d5, +} +d5 = { + **u5m, # E: Cannot unpack item that may contain extra keys into a closed TypedDict +} +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictFinalAndClassVar] from typing import TypedDict, Final, ClassVar diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index b6c6b5ce0011e..65ee837452f5b 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -213,8 +213,7 @@ def test5(t: tuple[A | B, ...]) -> None: def test6(t: tuple[B, Unpack[tuple[A | B, ...]], B]) -> None: if is_tuple_of_B(t): - # Should this be tuple[B, *tuple[B, ...], B] - reveal_type(t) # N: Revealed type is "tuple[__main__.B, Never, __main__.B]" + reveal_type(t) # N: Revealed type is "tuple[__main__.B, Unpack[builtins.tuple[__main__.B, ...]], __main__.B]" else: reveal_type(t) # N: Revealed type is "tuple[__main__.B, Unpack[builtins.tuple[__main__.A | __main__.B, ...]], __main__.B]" [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test index 535d882ccf3c4..a5cdaea16105c 100644 --- a/test-data/unit/check-typevar-defaults.test +++ b/test-data/unit/check-typevar-defaults.test @@ -137,8 +137,7 @@ def func_error_alias1( reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, Any]" reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.float]" -TERR2 = Dict[T4, T3] # TODO should be an error \ - # Type parameter "T4" has a default type that refers to one or more type variables that are out of scope +TERR2 = Dict[T4, T3] # E: Type parameter "T4" has a default type that refers to one or more type variables that are out of scope def func_error_alias2( a: TERR2, @@ -150,6 +149,15 @@ def func_error_alias2( reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.float]" [builtins fixtures/dict.pyi] +[case testTypeVarDefaultsExplicitNever] +from typing import Generic, Never, TypeVar + +D = TypeVar("D", default=int) +class WithDefault(Generic[D]): pass + +never_with_default: WithDefault[Never] = WithDefault() +explicit_with_default: WithDefault[Never] = WithDefault[Never]() + [case testTypeVarDefaultsFunctions] from typing import TypeVar, ParamSpec, List, Union, Callable, Tuple from typing_extensions import TypeVarTuple, Unpack @@ -354,7 +362,7 @@ def func_c1( a: ClassC1, b: ClassC1[float], ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]" k = ClassC1() @@ -367,18 +375,14 @@ class ClassC2(Generic[T3, Unpack[Ts3]]): ... def func_c2( a: ClassC2, b: ClassC2[int], - c: ClassC2[int, Unpack[Tuple[()]]], ) -> None: reveal_type(a) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" - # reveal_type(b) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - reveal_type(c) # N: Revealed type is "__main__.ClassC2[builtins.int]" + reveal_type(b) # N: Revealed type is "__main__.ClassC2[builtins.int]" k = ClassC2() reveal_type(k) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" l = ClassC2[int]() - # reveal_type(l) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - m = ClassC2[int, Unpack[Tuple[()]]]() - reveal_type(m) # N: Revealed type is "__main__.ClassC2[builtins.int]" + reveal_type(l) # N: Revealed type is "__main__.ClassC2[builtins.int]" class ClassC3(Generic[T3, Unpack[Ts4]]): ... @@ -387,7 +391,7 @@ def func_c3( b: ClassC3[int], c: ClassC3[int, Unpack[Tuple[float]]] ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC3[builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC3[builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC3[builtins.int]" reveal_type(c) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" @@ -398,21 +402,24 @@ def func_c3( m = ClassC3[int, Unpack[Tuple[float]]]() reveal_type(m) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" -class ClassC4(Generic[T1, Unpack[Ts1], T3]): ... +class ClassC4(Generic[T1, Unpack[Ts1], T3]): # E: A type variable with default cannot follow TypeVarTuple + x: T3 + +reveal_type(ClassC4().x) # N: Revealed type is "Any" def func_c4( a: ClassC4, # E: Missing type arguments for generic type "ClassC4" b: ClassC4[int], c: ClassC4[int, float], ) -> None: - reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" - # reveal_type(b) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]]]" + reveal_type(b) # N: Revealed type is "__main__.ClassC4[builtins.int]" reveal_type(c) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" k = ClassC4() # E: Need type annotation for "k" - reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" + reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]]]" l = ClassC4[int]() - # reveal_type(l) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + reveal_type(l) # N: Revealed type is "__main__.ClassC4[builtins.int]" m = ClassC4[int, float]() reveal_type(m) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" [builtins fixtures/tuple.pyi] @@ -509,8 +516,8 @@ def func_d3( reveal_type(b) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]" reveal_type(c) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.float]" - # k = ClassD3() - # reveal_type(k) # Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]" # TODO + k = ClassD3() + reveal_type(k) # N: Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]" l = ClassD3[int]() reveal_type(l) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]" m = ClassD3[int, float]() @@ -670,7 +677,7 @@ def func_c1( a: TC1, b: TC1[float], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.float]" TC2 = Tuple[T3, Unpack[Ts3]] @@ -678,11 +685,9 @@ TC2 = Tuple[T3, Unpack[Ts3]] def func_c2( a: TC2, b: TC2[int], - c: TC2[int, Unpack[Tuple[()]]], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - # reveal_type(b) # Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - reveal_type(c) # N: Revealed type is "tuple[builtins.int]" + reveal_type(a) # N: Revealed type is "tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" + reveal_type(b) # N: Revealed type is "tuple[builtins.int]" TC3 = Tuple[T3, Unpack[Ts4]] @@ -691,20 +696,20 @@ def func_c3( b: TC3[int], c: TC3[int, Unpack[Tuple[float]]], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.int]" reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float]" -TC4 = Tuple[T1, Unpack[Ts1], T3] +TC4 = Tuple[T1, Unpack[Ts1], T3] # E: A type variable with default cannot follow TypeVarTuple def func_c4( a: TC4, # E: Missing type arguments for generic type "TC4" b: TC4[int], c: TC4[int, float], ) -> None: - reveal_type(a) # N: Revealed type is "tuple[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" - # reveal_type(b) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO - reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float]" + reveal_type(a) # N: Revealed type is "tuple[Any, Unpack[builtins.tuple[Any, ...]], Any]" + reveal_type(b) # N: Revealed type is "tuple[builtins.int, Any]" + reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float, Any]" [builtins fixtures/tuple.pyi] [case testTypeVarDefaultsTypeAliasRecursive1] @@ -895,6 +900,19 @@ Alias: TypeAlias = "MyClass[T1, T2]" class MyClass(Generic["T1", "T2"]): ... [builtins fixtures/tuple.pyi] +[case testTypeVariableDefaultNotSticky] +from typing import Generic, TypeVar + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=T1) + +class MyClass(Generic[T1, T2]): ... + +reveal_type(MyClass[int]) # N: Revealed type is "def () -> __main__.MyClass[builtins.int, builtins.int]" +reveal_type(MyClass[str]) # N: Revealed type is "def () -> __main__.MyClass[builtins.str, builtins.str]" +reveal_type(MyClass[bytes]) # N: Revealed type is "def () -> __main__.MyClass[builtins.bytes, builtins.bytes]" +[builtins fixtures/tuple.pyi] + [case testDefaultsApplicationInAliasNoCrashNested] from typing import Generic, TypeVar from typing_extensions import TypeAlias @@ -919,21 +937,129 @@ T3 = TypeVar("T3", default=T2) class A(Generic[T1, T2, T3]): ... reveal_type(A) # N: Revealed type is "def [T1, T2 = T1`1, T3 = T2`2 = T1`1] () -> __main__.A[T1`1, T2`2 = T1`1, T3`3 = T2`2 = T1`1]" a: A[int] -reveal_type(a) # N: Revealed type is "__main__.A[builtins.int, builtins.int, T1`1]" +reveal_type(a) # N: Revealed type is "__main__.A[builtins.int, builtins.int, builtins.int]" + +AA = tuple[T1, T2, T3] +aa: AA[str] +reveal_type(aa) # N: Revealed type is "tuple[builtins.str, builtins.str, builtins.str]" -class B(Generic[T1, T3]): ... # E: Type variable T2 referenced in the default of T3 is unbound +class B(Generic[T1, T3]): ... # E: Type parameter "T3" has a default type that refers to one or more type variables that are out of scope reveal_type(B) # N: Revealed type is "def [T1, T3 = Any] () -> __main__.B[T1`1, T3`2 = Any]" b: B[int] reveal_type(b) # N: Revealed type is "__main__.B[builtins.int, Any]" -class C(Generic[T2]): ... # E: Type variable T1 referenced in the default of T2 is unbound +class C(Generic[T2]): ... # E: Type parameter "T2" has a default type that refers to one or more type variables that are out of scope reveal_type(C) # N: Revealed type is "def [T2 = Any] () -> __main__.C[T2`1 = Any]" c: C reveal_type(c) # N: Revealed type is "__main__.C[Any]" -class D(Generic[T2, T1]): ... # E: Type variable T1 referenced in the default of T2 is unbound \ +class D(Generic[T2, T1]): ... # E: Type parameter "T2" has a default type that refers to one or more type variables that are out of scope \ # E: "T1" cannot appear after "T2" in type parameter list because it has no default type reveal_type(D) # N: Revealed type is "def [T2 = Any, T1 = Any] () -> __main__.D[T2`1 = Any, T1`2 = Any]" d: D reveal_type(d) # N: Revealed type is "__main__.D[Any, Any]" [builtins fixtures/tuple.pyi] + +[case testRecursiveTypeVarDefaultBasic] +from typing import TypeVar, Generic + +class C(Generic["T"]): + pass + +T = TypeVar("T", bound=C, default=C) + +c: C +reveal_type(c) # N: Revealed type is "__main__.C[__main__.C[Any]]" + +[case testRecursiveTypeVarDefaultMutual] +from typing import TypeVar, Generic + +class C(Generic["T"]): + pass + +class D(Generic["S"]): + pass + +T = TypeVar("T", default=D) +S = TypeVar("S", default=C) + +c: C +d: D +reveal_type(c) # N: Revealed type is "__main__.C[__main__.D[Any]]" +reveal_type(d) # N: Revealed type is "__main__.D[__main__.C[Any]]" + +[case testNonRecursiveSimpleTypeVarDefault] +from typing import TypeVar, Generic + +S = TypeVar("S", default="Parent") +class Child(Generic[S]): ... + +T = TypeVar("T", default=int) +class Parent(Generic[T]): ... + +reveal_type(Child()) # N: Revealed type is "__main__.Child[__main__.Parent[builtins.int]]" + +[case testNonRecursiveTypeVarDefaultImportCycleClass] +import exp +[file exp.pyi] +from typing import Generic, TypeVar +import ind + +T = TypeVar("T", bound=D, default=D) +class F(Generic[T]): + x: T + +class D(E): ... +class E: ... + +[file ind.pyi] +from exp import F + +class Ind(F): ... +x: Ind +reveal_type(x.x) # N: Revealed type is "exp.D" + +[case testNonRecursiveTypeVarDefaultImportCycleAlias] +import exp +[file exp.pyi] +from typing import TypeVar +import ind + +T = TypeVar("T", bound=D, default=D) +F = list[T] + +class D(E): ... +class E: ... + +[file ind.pyi] +from exp import F + +Ind = list[F] +x: Ind +reveal_type(x) # N: Revealed type is "builtins.list[builtins.list[exp.D]]" + +[case testRecursiveTypeVarDefaultClassAndAlias] +from typing import Generic, TypeVar, Union + +T = TypeVar("T", default="Pattern") + +class Trait(Generic[T]): + pass + +class Pattern1(Trait): + pass + +Pattern = Union[Pattern1, None] + +reveal_type(Trait()) # N: Revealed type is "__main__.Trait[__main__.Pattern1 | None]" +[builtins fixtures/tuple.pyi] + +[case testRecursiveTypeVarDefaultOnlyAlias] +from typing import TypeVar + +T = TypeVar("T", default="A") + +A = list[T] +a: A +reveal_type(a) # N: Revealed type is "builtins.list[builtins.list[Any]]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 703653227e200..7db0fa6f4388c 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -578,6 +578,7 @@ reveal_type(b) # N: Revealed type is "def (*Any) -> builtins.int" [builtins fixtures/tuple.pyi] [case testTypeVarTuplePep646CallableNewSyntax] +# flags: --python-version 3.11 from typing import Callable, Generic, Tuple from typing_extensions import ParamSpec @@ -882,7 +883,39 @@ z: C reveal_type(x) # N: Revealed type is "Any" reveal_type(y) # N: Revealed type is "Any" reveal_type(z) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[Any, ...]]]" +[builtins fixtures/tuple.pyi] + +[case testBanPathologicalRecursiveTuplesGeneric] +from typing import TypeVarTuple, Unpack +Ts = TypeVarTuple("Ts") +A = tuple[Unpack[B[Unpack[Ts]]]] # E: Invalid recursive alias: a tuple item of itself \ + # E: Name "B" is used before definition +B = tuple[Unpack[A[Unpack[Ts]]]] +[builtins fixtures/tuple.pyi] + +[case testNoCrashOnInvalidRecursiveUnpackOfUnion] +from typing import Unpack + +A = tuple[int, str] | list[tuple[Unpack[A]]] # E: "tuple[int, str] | list[tuple[Unpack[A]]]" cannot be unpacked (must be tuple or TypeVarTuple) +[builtins fixtures/tuple.pyi] + +[case testNoCrashOnNonNormalRecursiveTuple] +from typing import Unpack + +A = tuple[int, list[tuple[str, Unpack[A]]]] +a: A +x, y = a +y[0] = 1 # E: Incompatible types in assignment (expression has type "int", target has type "tuple[str, Unpack[A]]") +[builtins fixtures/list.pyi] + +[case testBanTypeVarTupleNotImmediatelyInsideUnpack] +from typing import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +A = tuple[Unpack[tuple[Ts]]] # E: TypeVarTuple "Ts" is only valid with an unpack +x: A[int, str] +reveal_type(x) # N: Revealed type is "tuple[Any]" [builtins fixtures/tuple.pyi] [case testInferenceAgainstGenericVariadicWithBadType] @@ -2145,17 +2178,17 @@ match(b) # E: Argument 1 to "match" has incompatible type "Bad"; expected "PC[U [builtins fixtures/tuple.pyi] [case testVariadicTupleCollectionCheck] -from typing import Tuple, Optional from typing_extensions import Unpack -allowed: Tuple[int, Unpack[Tuple[int, ...]]] +allowed: tuple[int, Unpack[tuple[int, ...]]] -x: Optional[int] -if x in allowed: - reveal_type(x) # N: Revealed type is "builtins.int" +def f(x: int | None): + if x in allowed: + reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] [case testJoinOfVariadicTupleCallablesNoCrash] +# flags: --python-version 3.11 from typing import Callable, Tuple f: Callable[[int, *Tuple[str, ...], int], None] @@ -2609,6 +2642,7 @@ def test(xs: tuple[Unpack[Ts]], xsi: tuple[int, Unpack[Ts]]) -> None: [builtins fixtures/tuple.pyi] [case testTypeVarTupleInferAgainstAnyCallableSuffix] +# flags: --python-version 3.11 from typing import Any, Callable, TypeVar, TypeVarTuple Ts = TypeVarTuple("Ts") @@ -2621,6 +2655,7 @@ reveal_type(deco(untyped)) # N: Revealed type is "def (*Any) -> Any" [builtins fixtures/tuple.pyi] [case testNoCrashOnNonNormalUnpackInCallable] +# flags: --python-version 3.11 from typing import Callable, Unpack, TypeVar T = TypeVar("T") @@ -2766,3 +2801,123 @@ def func(d: Callable[[Unpack[Ts]], T]) -> T: ... y = func[1, int] # E: Type application is only supported for generic classes \ # E: Invalid type: try using Literal[1] instead? [builtins fixtures/tuple.pyi] + +[case testTypeVarTupleUnpackWithoutArgsInProtocol] +# https://github.com/python/mypy/issues/21467 +from typing import Protocol, Unpack + +class C(Protocol[Unpack]): # E: Free type variable expected in Protocol[...] + pass +[builtins fixtures/tuple.pyi] + +[case testGenericTupleSubclassConstructor] +from typing import TypeVar, TypeVarTuple, Unpack + +T = TypeVar("T") +S = TypeVar("S") +Ts = TypeVarTuple("Ts") + +class S1(tuple[T, S]): ... +class S2(tuple[Unpack[Ts]]): ... + +reveal_type(S1((1, ""))) # N: Revealed type is "tuple[builtins.int, builtins.str, fallback=__main__.S1[builtins.int, builtins.str]]" +reveal_type(S1((1, "", 2))) # N: Revealed type is "tuple[Never, Never, fallback=__main__.S1[Never, Never]]" \ + # E: Argument 1 to "S1" has incompatible type "tuple[int, str, int]"; expected "tuple[Never, Never]" + +a1: S1[int, str] = S1((1, "")) +b1: S1[int, str] = S1((1, 1)) # E: Argument 1 to "S1" has incompatible type "tuple[int, int]"; expected "tuple[int, str]" +c1: S1[int, str] = S1((1, "", 2)) # E: Argument 1 to "S1" has incompatible type "tuple[int, str, int]"; expected "tuple[int, str]" + +reveal_type(S2(())) # N: Revealed type is "tuple[(), fallback=__main__.S2[()]]" +reveal_type(S2((1, ""))) # N: Revealed type is "tuple[Literal[1]?, Literal['']?, fallback=__main__.S2[Literal[1]?, Literal['']?]]" +reveal_type(S2((1, "", 2))) # N: Revealed type is "tuple[Literal[1]?, Literal['']?, Literal[2]?, fallback=__main__.S2[Literal[1]?, Literal['']?, Literal[2]?]]" + +a2: S2[int, str] = S2((1, "")) +b2: S2[int, str] = S2((1, 1)) # E: Argument 1 to "S2" has incompatible type "tuple[int, int]"; expected "tuple[int, str]" +c2: S2[int, str] = S2((1, "", 2)) # E: Argument 1 to "S2" has incompatible type "tuple[int, str, int]"; expected "tuple[int, str]" + +t: S2[Unpack[tuple[int, ...]]] = S2((1, 2)) +t_bad: S2[Unpack[tuple[int, ...]]] = S2((1, "")) # E: Argument 1 to "S2" has incompatible type "tuple[int, str]"; expected "tuple[int, ...]" +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleSubclassEmptyIndex] +from typing import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Shape(tuple[Unpack[Ts]]): ... + +class Shape0(Shape[()]): ... + +x: Shape[()] +y: Shape0 + +reveal_type(x) # N: Revealed type is "tuple[(), fallback=__main__.Shape[()]]" +reveal_type(y) # N: Revealed type is "tuple[(), fallback=__main__.Shape0]" + +def test(t: tuple[int, ...]) -> None: ... +test(x) +test(y) +[builtins fixtures/tuple.pyi] + +[case testVariadicTupleSubclassVariadicIndex] +from typing import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Shape(tuple[Unpack[Ts]]): ... + +class ShapeN(Shape[Unpack[tuple[int, ...]]]): ... + +x: Shape[Unpack[tuple[int, ...]]] +y: ShapeN + +def test(t: tuple[int, ...]) -> None: ... +test(x) +test(y) +[builtins fixtures/tuple.pyi] + +[case testVariadicGenericClassEmptyTupleUnpackIndex] +from typing import Generic, TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class Blah(Generic[Unpack[Ts]]): + pass + +x: Blah[()] +y: Blah[Unpack[tuple[()]]] + +Empty = tuple[()] +z: Blah[Unpack[Empty]] + +reveal_type(x) # N: Revealed type is "__main__.Blah[()]" +reveal_type(y) # N: Revealed type is "__main__.Blah[()]" +reveal_type(z) # N: Revealed type is "__main__.Blah[()]" +[builtins fixtures/tuple.pyi] + +[case testVariadicGenericAliasEmptyTupleUnpackIndex] +from typing import Generic, TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +Blah = list[tuple[Unpack[Ts]]] + +x: Blah[()] +y: Blah[Unpack[tuple[()]]] + +Empty = tuple[()] +z: Blah[Unpack[Empty]] + +reveal_type(x) # N: Revealed type is "builtins.list[tuple[()]]" +reveal_type(y) # N: Revealed type is "builtins.list[tuple[()]]" +reveal_type(z) # N: Revealed type is "builtins.list[tuple[()]]" +[builtins fixtures/tuple.pyi] + +[case testMeetTupleWithUnpackVsInstanceWorks_no_verbose_reveal] +from typing import TypeVar, Unpack, Union + +K = TypeVar("K") +Prefix = tuple[Union[K, list[int]], ...] + +def g(x: K) -> None: + d: Union[Prefix[K], tuple[Unpack[Prefix[int]], int], None] = None + assert d is not None + reveal_type(d) # N: Revealed type is "tuple[K | list[int], ...] | tuple[*tuple[int | list[int], ...], int]" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-unbound.test b/test-data/unit/check-typevar-unbound.test index 79d326f9fedf3..2dffaee6f2fe7 100644 --- a/test-data/unit/check-typevar-unbound.test +++ b/test-data/unit/check-typevar-unbound.test @@ -13,6 +13,9 @@ def g() -> U: # E: A function returning TypeVar should receive at least one argu # N: Consider using the upper bound "int" instead ... +def g2() -> U: # type: ignore[type-var] + ... + V = TypeVar('V', int, str) def h() -> V: # E: A function returning TypeVar should receive at least one argument containing the same TypeVar diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 172e57cf1a4b3..4de3d649c0686 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -1116,6 +1116,7 @@ class D: [builtins fixtures/dict.pyi] [case testUnpackInCallableType] +# flags: --python-version 3.11 from typing import Callable, TypedDict from typing_extensions import Unpack diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index eb8f4931fa2fe..7066034b3e39c 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -363,11 +363,11 @@ mypy: error: Mypy no longer supports checking Python 2 code. Consider pinning to python_version = 3.10 [out] -[case testPythonVersionAccepted314] +[case testPythonVersionAccepted315] # cmd: mypy -c pass [file mypy.ini] \[mypy] -python_version = 3.14 +python_version = 3.15 [out] [case testPythonVersionFallback] @@ -530,6 +530,35 @@ variable has type "bytes") b: bytes = 1 ^ +[case testShadowFileWithNativeParser] +# cmd: mypy --native-parser --shadow-file source.py shadow.py source.py +[file source.py] +x: int = 1 +reveal_type(x) +[file shadow.py] +x: str = "y" +reveal_type(x) +[out] +source.py:2: note: Revealed type is "str" +== Return code: 0 + +[case testShadowFileWithNativeParserParallel] +# cmd: mypy --native-parser --num-workers=4 --shadow-file a.py a_shadow.py main.py a.py b.py +[file main.py] +import a +import b +[file a.py] +x: int = 1 +reveal_type(x) +[file b.py] +y: int = 2 +[file a_shadow.py] +x: str = "y" +reveal_type(x) +[out] +a.py:2: note: Revealed type is "str" +== Return code: 0 + [case testConfigWarnUnusedSection1] # cmd: mypy foo.py quux.py spam/eggs.py [file mypy.ini] diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index ab2c5f1c43e74..6850eabf09b6a 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -69,6 +69,19 @@ x = 1 -> m.f -> m, m.f +[case testAccessReexportedModuleAttribute] +import pkg +pkg.f() +[file pkg/__init__.py] +from pkg.sub import * +[file pkg/sub.py] +def f() -> None: pass +[out] + -> m + -> m + -> pkg + -> m + [case testImport] import n [file n.py] diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 1f1987183fe46..c4bbf7d3f2283 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -676,6 +676,24 @@ p = Point(dict(x=42, y=1337)) __main__.Point __main__.p +[case testTypedDict5] +from typing import TypedDict +class Point(TypedDict): + x: int + y: int +p = Point(dict(x=42, y=1337)) +[file next.py] +from typing import TypedDict +class Point(TypedDict, closed=True): + x: int + y: int +p = Point(dict(x=42, y=1337)) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] +[out] +__main__.Point +__main__.p + [case testTypeAliasSimple] A = int B = int diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 3e21a77db3431..ef2e8c0b343bf 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -3745,6 +3745,83 @@ b.py:4: error: ReadOnly TypedDict key "y" TypedDict is mutated == b.py:3: error: ReadOnly TypedDict key "x" TypedDict is mutated +[case testTypedDictUpdateClosed] +import b +[file a.py] +from typing import TypedDict, Union +class A(TypedDict): + a: int +class B(TypedDict, closed=True): + b: int +C = Union[A, B] +[file a.py.2] +from typing import TypedDict, Union +class A(TypedDict, closed=True): + a: int +class B(TypedDict): + b: int +C = Union[A, B] +[file a.py.3] +from typing import TypedDict, Union +class A(TypedDict, closed=True): + a: int +class B(TypedDict, closed=True): + b: int +C = Union[A, B] +[file b.py] +from a import C +def foo(x: C) -> int: + if "b" in x: + return x["b"] + else: + return x["a"] +def bar(x: C) -> int: + if "a" in x: + return x["a"] + else: + return x["b"] +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] +[out] +b.py:4: error: TypedDict "A" has no key "b" +== +b.py:9: error: TypedDict "B" has no key "a" +== + +[case testTypedDictUpdateClosedPassingToFailing] +import b +[file a.py] +from typing import TypedDict, Union +class A(TypedDict, closed=True): + a: int +class B(TypedDict, closed=True): + b: int +C = Union[A, B] +[file a.py.2] +from typing import TypedDict, Union +class A(TypedDict): + a: int +class B(TypedDict, closed=True): + b: int +C = Union[A, B] +[file b.py] +from a import C +def foo(x: C) -> int: + if "b" in x: + return x["b"] + else: + return x["a"] +def bar(x: C) -> int: + if "a" in x: + return x["a"] + else: + return x["b"] +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] +[out] +== +b.py:4: error: TypedDict "A" has no key "b" + [case testBasicAliasUpdate] import b [file a.py] @@ -11762,3 +11839,134 @@ def bar() -> str: return "a" main:2: note: Revealed type is "None | builtins.int" == main:2: note: Revealed type is "None | builtins.str" + +[case testStarExportedNameDeleted1] +import pkg + +pkg.loads() + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +def loads() -> None: ... + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: "object" has no attribute "loads" + +[case testStarExportedNameDeleted2] +import pkg + +pkg.C() + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +class C: pass + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: "object" has no attribute "C" + +[case testStarExportedNameDeleted3] +from pkg import loads + +loads() + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +def loads() -> None: ... + +[file pkg/sub.pyi.2] +[out] +== +main:1: error: Module "pkg" has no attribute "loads" + +[case testStarExportedNameDeleted4] +from pkg import sub + +a = sub.x + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +x = 1 + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: "object" has no attribute "x" + +[case testStarExportedNameDeleted5] +from pkg import sub + +x: sub.N = 1 + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +N = int + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: Name "sub.N" is not defined + +[case testStarExportedNameDeleted6] +from pkg import * + +loads() + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +def loads() -> None: ... + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: Name "loads" is not defined + +[case testStarExportedNameDeleted7] +from pkg import * + +C() + +[file pkg/__init__.pyi] +from .sub import * + +[file pkg/sub.pyi] +class C: pass + +[file pkg/sub.pyi.2] +[out] +== +main:3: error: Name "C" is not defined + +[case testStarExportedNameDeleted8] +from a import * + +C() + +[file a.py] +from b import * + +[file b.py] +from c import * + +[file c.py] +class C: pass + +[file c.py.2] +[out] +== +main:3: error: Name "C" is not defined diff --git a/test-data/unit/native-parser-python311.test b/test-data/unit/native-parser-python311.test new file mode 100644 index 0000000000000..9a314715133a0 --- /dev/null +++ b/test-data/unit/native-parser-python311.test @@ -0,0 +1,108 @@ +[case testTryExceptStar] +try: + x +except* ValueError: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryExceptStarWithVar] +try: + x +except* Exception as e: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(Exception) + NameExpr(e) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryMultipleExceptStar] +try: + x +except* ValueError: + y +except* KeyError as e: + z +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + NameExpr(KeyError) + NameExpr(e) + Block:6( + ExpressionStmt:6( + NameExpr(z))))) + +[case testTryExceptStarElseFinally] +try: + x +except* ValueError: + y +else: + z +finally: + w +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + Else( + ExpressionStmt:6( + NameExpr(z))) + Finally( + ExpressionStmt:8( + NameExpr(w))))) + +[case testUnpackTypeInSignature] +def f(*args: *Ts) -> None: + pass +[out] +MypyFile:1( + FuncDef:1( + f + def (*args: *Ts?) -> None? + VarArg( + Var(args)) + Block:2( + PassStmt:2()))) + +[case testUnpackTypeInTuple] +x: tuple[int, *Ts, str] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + tuple?[int?, *Ts?, str?])) diff --git a/test-data/unit/native-parser-python312.test b/test-data/unit/native-parser-python312.test new file mode 100644 index 0000000000000..2b1f9b42e0f78 --- /dev/null +++ b/test-data/unit/native-parser-python312.test @@ -0,0 +1,90 @@ +[case testPEP695TypeAlias] +# comment +type A[T] = C[T] +[out] +MypyFile:1( + TypeAliasStmt:2( + NameExpr(A) + TypeParam( + T) + LambdaExpr:2( + Block:-1( + ReturnStmt:2( + IndexExpr:2( + NameExpr(C) + NameExpr(T))))))) + +[case testPEP695GenericFunction] +# comment + +def f[T](): pass +def g[T: str](): pass +def h[T: (int, str)](): pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + T) + Block:3( + PassStmt:3())) + FuncDef:4( + g + TypeParam( + T + str?) + Block:4( + PassStmt:4())) + FuncDef:5( + h + TypeParam( + T + Values( + int? + str?)) + Block:5( + PassStmt:5()))) + +[case testPEP695ParamSpec] +# comment + +def f[**P](): pass +class C[T: int, **P]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + **P) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + **P) + PassStmt:4())) + +[case testPEP695TypeVarTuple] +# comment + +def f[*Ts](): pass +class C[T: int, *Ts]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + *Ts) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + *Ts) + PassStmt:4())) diff --git a/test-data/unit/native-parser.test b/test-data/unit/native-parser.test index 8962875a9cf27..abf653fc213c1 100644 --- a/test-data/unit/native-parser.test +++ b/test-data/unit/native-parser.test @@ -1294,92 +1294,6 @@ MypyFile:1( ExpressionStmt:6( NameExpr(z))))) -[case testTryExceptStar] -try: - x -except* ValueError: - y -[out] -MypyFile:1( - TryStmt:1( - Block:2( - ExpressionStmt:2( - NameExpr(x))) - * - NameExpr(ValueError) - Block:4( - ExpressionStmt:4( - NameExpr(y))))) - -[case testTryExceptStarWithVar] -try: - x -except* Exception as e: - y -[out] -MypyFile:1( - TryStmt:1( - Block:2( - ExpressionStmt:2( - NameExpr(x))) - * - NameExpr(Exception) - NameExpr(e) - Block:4( - ExpressionStmt:4( - NameExpr(y))))) - -[case testTryMultipleExceptStar] -try: - x -except* ValueError: - y -except* KeyError as e: - z -[out] -MypyFile:1( - TryStmt:1( - Block:2( - ExpressionStmt:2( - NameExpr(x))) - * - NameExpr(ValueError) - Block:4( - ExpressionStmt:4( - NameExpr(y))) - NameExpr(KeyError) - NameExpr(e) - Block:6( - ExpressionStmt:6( - NameExpr(z))))) - -[case testTryExceptStarElseFinally] -try: - x -except* ValueError: - y -else: - z -finally: - w -[out] -MypyFile:1( - TryStmt:1( - Block:2( - ExpressionStmt:2( - NameExpr(x))) - * - NameExpr(ValueError) - Block:4( - ExpressionStmt:4( - NameExpr(y))) - Else( - ExpressionStmt:6( - NameExpr(z))) - Finally( - ExpressionStmt:8( - NameExpr(w))))) - [case testConditionalExprSimple] x if y else z [out] @@ -2018,30 +1932,6 @@ MypyFile:1( y)) PassStmt:5())) -[case testUnpackTypeInSignature] -def f(*args: *Ts) -> None: - pass -[out] -MypyFile:1( - FuncDef:1( - f - def (*args: *Ts?) -> None? - VarArg( - Var(args)) - Block:2( - PassStmt:2()))) - -[case testUnpackTypeInTuple] -x: tuple[int, *Ts, str] -[out] -MypyFile:1( - AssignmentStmt:1( - NameExpr(x) - TempNode:1( - Any) - tuple?[int?, *Ts?, str?])) - - [case testLiteralStringType] from typing import Literal x: Literal["hello"] @@ -3186,97 +3076,6 @@ MypyFile:1( Body( PassStmt:3()))) -[case testPEP695TypeAlias] -# comment -type A[T] = C[T] -[out] -MypyFile:1( - TypeAliasStmt:2( - NameExpr(A) - TypeParam( - T) - LambdaExpr:2( - Block:-1( - ReturnStmt:2( - IndexExpr:2( - NameExpr(C) - NameExpr(T))))))) - -[case testPEP695GenericFunction] -# comment - -def f[T](): pass -def g[T: str](): pass -def h[T: (int, str)](): pass -[out] -MypyFile:1( - FuncDef:3( - f - TypeParam( - T) - Block:3( - PassStmt:3())) - FuncDef:4( - g - TypeParam( - T - str?) - Block:4( - PassStmt:4())) - FuncDef:5( - h - TypeParam( - T - Values( - int? - str?)) - Block:5( - PassStmt:5()))) - -[case testPEP695ParamSpec] -# comment - -def f[**P](): pass -class C[T: int, **P]: pass -[out] -MypyFile:1( - FuncDef:3( - f - TypeParam( - **P) - Block:3( - PassStmt:3())) - ClassDef:4( - C - TypeParam( - T - int?) - TypeParam( - **P) - PassStmt:4())) - -[case testPEP695TypeVarTuple] -# comment - -def f[*Ts](): pass -class C[T: int, *Ts]: pass -[out] -MypyFile:1( - FuncDef:3( - f - TypeParam( - *Ts) - Block:3( - PassStmt:3())) - ClassDef:4( - C - TypeParam( - T - int?) - TypeParam( - *Ts) - PassStmt:4())) - [case testTypeCommentLastLine] d = [ # Placeholder for items diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 659a8515455d3..8c37e48d7c332 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -2268,3 +2268,45 @@ _testNarrowingMappingAndAbstractSet.py:33: note: Revealed type is "dict[str, int _testNarrowingMappingAndAbstractSet.py:34: note: Revealed type is "set[str]" _testNarrowingMappingAndAbstractSet.py:38: note: Revealed type is "_collections_abc.dict_keys[str, int]" _testNarrowingMappingAndAbstractSet.py:39: note: Revealed type is "set[str]" + +[case testNativeParserTStringRequiresPython314] +# flags: --python-version=3.13 --native-parser --ignore-missing-imports +x = t"hello {1}" +reveal_type(x) +[out] +_testNativeParserTStringRequiresPython314.py:2: error: T-strings: requires Python 3.14 or newer (current target: Python 3.13) +_testNativeParserTStringRequiresPython314.py:3: note: Revealed type is "Any" + +[case testNativeParserTStringRequiresPython314InStub] +# flags: --python-version=3.13 --native-parser --ignore-missing-imports +import m +[file m.pyi] +x: object +def f() -> object: ... +y = t"hello {1}" +[out] +m.pyi:3: error: T-strings: requires Python 3.14 or newer (current target: Python 3.13) + +[case testNativeParserStarUnpackRequiresPython311] +# flags: --python-version=3.10 --native-parser +from typing_extensions import TypeVarTuple, Unpack +Ts = TypeVarTuple("Ts") +def f(x: tuple[Unpack[Ts]]) -> None: ... +def g(x: tuple[*Ts]) -> None: ... +reveal_type(g) +g((1, 2)) +[out] +_testNativeParserStarUnpackRequiresPython311.py:5: error: Star unpack syntax: requires Python 3.11 or newer (current target: Python 3.10) +_testNativeParserStarUnpackRequiresPython311.py:6: note: Revealed type is "def [Ts] (x: tuple[*Ts])" + +[case testNativeParserPEP695InStubNoVersionError] +# flags: --python-version=3.11 --native-parser --ignore-missing-imports +import m +reveal_type(m.f) +[file m.pyi] +class C[T]: ... +def f[T](x: T) -> T: ... +type Alias[T] = list[T] +def g[T = int](x: T) -> T: ... +[out] +_testNativeParserPEP695InStubNoVersionError.py:3: note: Revealed type is "def [T] (x: T) -> T" diff --git a/test-data/unit/semanal-namedtuple.test b/test-data/unit/semanal-namedtuple.test index 62bd87f1995a6..a4b8df00b9a3f 100644 --- a/test-data/unit/semanal-namedtuple.test +++ b/test-data/unit/semanal-namedtuple.test @@ -121,9 +121,9 @@ MypyFile:1( ClassDef:2( A TupleType( - tuple[Any, fallback=__main__.N@2]) + tuple[Any, fallback=__main__.A@base1]) BaseType( - __main__.N@2) + __main__.A@base1) PassStmt:2())) [case testNamedTupleBaseClassWithItemTypes] @@ -136,9 +136,9 @@ MypyFile:1( ClassDef:2( A TupleType( - tuple[builtins.int, fallback=__main__.N@2]) + tuple[builtins.int, fallback=__main__.A@base1]) BaseType( - __main__.N@2) + __main__.A@base1) PassStmt:2())) -- Errors @@ -239,9 +239,9 @@ MypyFile:1( ClassDef:4( A TupleType( - tuple[builtins.int, fallback=__main__.N@4]) + tuple[builtins.int, fallback=__main__.A@base1]) Decorators( NameExpr(final [typing.final])) BaseType( - __main__.N@4) + __main__.A@base1) PassStmt:5())) diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 161f14e8aea77..0c8b74ecf29a1 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -3822,16 +3822,26 @@ def f(x: str | None) -> None: ... [case testTypeddict] import typing, x -X = typing.TypedDict('X', {'a': int, 'b': str}) -Y = typing.TypedDict('X', {'a': int, 'b': str}, total=False) +D1 = typing.TypedDict('D1', {'a': int, 'b': str}) +D2 = typing.TypedDict('D2', {'a': int, 'b': str}, total=False) +D3 = typing.TypedDict('D3', {'a': int, 'b': str}, closed=True) +D4 = typing.TypedDict('D4', {'a': int, 'b': str}, closed=True, total=False) [out] from typing_extensions import TypedDict -class X(TypedDict): +class D1(TypedDict): a: int b: str -class Y(TypedDict, total=False): +class D2(TypedDict, total=False): + a: int + b: str + +class D3(TypedDict, closed=True): + a: int + b: str + +class D4(TypedDict, total=False, closed=True): a: int b: str diff --git a/test-requirements.txt b/test-requirements.txt index 8ac31e0b34666..73d5966040c4a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,72 +4,77 @@ # # pip-compile --allow-unsafe --output-file=test-requirements.txt --strip-extras test-requirements.in # -ast-serialize==0.3.0 - # via -r test-requirements.in -attrs==25.4.0 +ast-serialize==0.6.0 + # via -r mypy-requirements.txt +attrs==26.1.0 # via -r test-requirements.in -cfgv==3.4.0 +cfgv==3.5.0 # via pre-commit -coverage==7.10.7 +coverage==7.13.5 # via pytest-cov distlib==0.4.0 # via virtualenv -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -filelock==3.20.0 +filelock==3.29.0 # via # -r test-requirements.in + # python-discovery # virtualenv -identify==2.6.15 +identify==2.6.19 # via pre-commit -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest -librt==0.10.0 ; platform_python_implementation != "PyPy" +librt==0.13.0 ; platform_python_implementation != "PyPy" # via -r mypy-requirements.txt -lxml==6.0.2 ; python_version < "3.15" +lxml==6.1.0 ; python_version < "3.15" # via -r test-requirements.in mypy-extensions==1.1.0 # via -r mypy-requirements.txt -nodeenv==1.9.1 +nodeenv==1.10.0 # via pre-commit -packaging==25.0 +packaging==26.2 # via pytest -pathspec==1.0.0 +pathspec==1.1.1 # via -r mypy-requirements.txt -platformdirs==4.5.0 - # via virtualenv +platformdirs==4.9.6 + # via + # python-discovery + # virtualenv pluggy==1.6.0 # via # pytest # pytest-cov -pre-commit==4.3.0 +pre-commit==4.6.0 # via -r test-requirements.in -psutil==7.1.0 +psutil==7.2.2 # via -r test-requirements.in -pygments==2.19.2 +pygments==2.20.0 # via pytest -pytest==8.4.2 +pytest==9.0.3 # via # -r test-requirements.in # pytest-cov # pytest-xdist -pytest-cov==7.0.0 +pytest-cov==7.1.0 # via -r test-requirements.in pytest-xdist==3.8.0 # via -r test-requirements.in +python-discovery==1.3.0 + # via virtualenv pyyaml==6.0.3 # via pre-commit -tomli==2.3.0 +tomli==2.4.1 # via -r test-requirements.in -types-psutil==7.0.0.20251001 +types-psutil==7.2.2.20260508 # via -r build-requirements.txt -types-setuptools==80.9.0.20250822 +types-setuptools==82.0.0.20260508 # via -r build-requirements.txt typing-extensions==4.15.0 # via -r mypy-requirements.txt -virtualenv==20.34.0 +virtualenv==21.3.1 # via pre-commit # The following packages are considered to be unsafe in a requirements file: -setuptools==80.9.0 +setuptools==82.0.1 # via -r test-requirements.in diff --git a/tox.ini b/tox.ini index 2126970afa991..ab81b00d121f5 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ envlist = py312, py313, py314, + py315, docs, lint, type,