Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b0ed48a
Document six more TypeVarCheck non-goals and drop their TODOs and xfa…
FrancescoPezzella Oct 13, 2026
29f21c8
Temporary UTF-8 fix; TODO drops the explicit encoding with Python 3.1…
FrancescoPezzella Oct 13, 2026
3ea6751
Skip functions that would mix legacy and PEP 695 type parameters. A f…
FrancescoPezzella Oct 13, 2026
143f4bd
Bind each TypeVar to the function whose signature uses it. A TypeVar …
FrancescoPezzella Oct 13, 2026
26a27b7
Place localized TypeVar declarations after the import block and run r…
FrancescoPezzella Oct 14, 2026
89f1a98
Copy the trailing comment of a localized TypeVar declaration
FrancescoPezzella Oct 14, 2026
5f14bb4
Import the names a localized TypeVar uses from where the origin gets …
FrancescoPezzella Oct 14, 2026
d152856
Decided that CLRF -> LF is a good thing, and not something that needs…
FrancescoPezzella Oct 19, 2026
9f96c87
List files in the TypeVar migration report with a blank line between …
FrancescoPezzella Oct 19, 2026
391eada
Document the qualified TypeVar constructor and src-layout absolute im…
FrancescoPezzella Oct 19, 2026
b1367ca
Shorten the line-ending and new non-goal documentation to the essenti…
FrancescoPezzella Oct 19, 2026
6b768c1
Add per-kind and ranked unsafe-reason summary tables to the TypeVar m…
FrancescoPezzella Oct 19, 2026
f152405
Report a type parameter still used by a generic class even when its f…
FrancescoPezzella Oct 19, 2026
034ce44
Type-Parameter-Modernization doc fix regarding Ruff change
FrancescoPezzella Oct 19, 2026
5c03ac5
Convert TypeVars declared with covariant=/contravariant= when only fu…
FrancescoPezzella Oct 19, 2026
1ef5a5b
List a type parameter that is localized and then removed only as an u…
FrancescoPezzella Oct 19, 2026
799de1a
Rename the declaration-name-unavailable reason to name-not-at-module-…
FrancescoPezzella Oct 19, 2026
00be49a
Reject --py below 3.12 and remove the PEP 695 version gate. Minimum s…
FrancescoPezzella Oct 20, 2026
b174911
Add --ignore-localize to keep the imports of shared type parameter fi…
FrancescoPezzella Oct 20, 2026
245efc7
Reword the conditional-origin-import reason to cover TYPE_CHECKING bl…
FrancescoPezzella Oct 20, 2026
6b97605
Make the type-parameter CLI's closing commit message list what was co…
FrancescoPezzella Oct 20, 2026
149c954
Replace --ignore-localize with --ignore, which leaves the named files…
FrancescoPezzella Oct 20, 2026
86b81f1
Add --no-localization to keep the imports of project type parameters …
FrancescoPezzella Oct 20, 2026
31e6776
Group the manual-review section of the migration report by reason and…
FrancescoPezzella Oct 20, 2026
57b62cf
Add --second-pass to reprocess files that kept a declaration only bec…
FrancescoPezzella Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/developer/feature-test-map/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ https://github.com/TNO/Renaissance.Py/blob/main/LICENSE
- `test/recipes/test_type_var_check_localize.py`
- `test/recipes/test_type_var_check_orphaned.py`
- `test/recipes/test_type_var_check_properties.py`
- `test/recipes/test_type_var_check_variance.py`
- `test/recipes/test_type_var_domain.py`
- `test/recipes/test_step_runner.py`
- `test/recipes/test_python_refactoring.py`
Expand Down
67 changes: 48 additions & 19 deletions docs/developer/modules/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ safely cover. This page covers `TypeVarCheck`, the recipe behind
| `convert_declared_typevars()` (only adds type parameters) | `converted_unsafe_reasons` |
| `remove_orphaned_declarations()` (only removes declarations) | `orphaned_unsafe_reasons` |

- `type_param_kinds`: maps each type parameter name the phases see to `"TypeVar"`, `"ParamSpec"` or `"TypeVarTuple"`.
- Attributes set by the CLI (`migration-type-recipes.py`): `min_python`, `project_root` (falls back to the file's
folder) and `project_wide_imported_names` (from `collect_project_imported_names`).
- `PythonRefactoring.process("TypeVarCheck", file)`, used by `cli.py refactor`, never sets those attributes, so every
version-gated rewrite is reported `"unsafe"` there.
folder), `project_wide_imported_names` (from `collect_project_imported_names`), `ignore_localize_origins` (absolute
paths of the files `--ignore` names) and `localize_imports` (`False` with `--no-localization`). `ignored_names` holds
the names this file imports from those files, or all its imported names when `localize_imports` is `False`.
- `PythonRefactoring.process("TypeVarCheck", file)`, used by `cli.py refactor`, never sets those attributes, so
`min_python` stays `MIN_SUPPORTED_PYTHON` there and a `default=` declaration is reported `"unsafe"`.

## Internal structure

Expand All @@ -54,12 +57,14 @@ safely cover. This page covers `TypeVarCheck`, the recipe behind
replaces only the node it changes, so formatting and comments elsewhere are kept.

- **Unsafe reasons.** `is_safe_to_remove`/`is_safe_to_localize` and `_conversion_refusal` return an `UnsafeReason`
or `None`. Each of the 9 reasons has an `UnsafeRule` (message + docs anchor) in `UNSAFE_RULES`; `doc_link()` turns
it into the URL that the CLI report prints.
or `None`. Each of the 8 reasons has an `UnsafeRule` (message + docs anchor) in `UNSAFE_RULES`. The CLI report prints
each message and links `DOCS_BASE_URL` once.

- **Version gates.** `_conversion_refusal` checks `min_python` against `PEP_695_MINIMUM = (3, 12)` and, for a
`default=`, `PEP_696_MINIMUM = (3, 13)`. `None` never passes. A keyword outside `_CONVERTIBLE_KEYWORDS` is refused
(`NO_PEP695_EQUIVALENT`) instead of dropped.
- **Version gates.** `min_python` is never below `MIN_SUPPORTED_PYTHON = (3, 12)`, which the CLI enforces, so PEP 695
syntax is always available. `_conversion_refusal` checks it against `PEP_696_MINIMUM = (3, 13)` for a
`default=`. A keyword outside `_CONVERTIBLE_KEYWORDS` is refused
(`NO_PEP695_EQUIVALENT`) instead of dropped. `covariant` and `contravariant` are in the set and are dropped, because
a function's type parameter has no variance; converting classes would have to refuse them.

- **Uses.** `uses_of` takes a declaration's uses from libcst's scope analysis (`referenced_by`). A use inside a
string annotation or `cast("T", x)` counts; a `T` that a PEP 695 type parameter of an enclosing function or class
Expand Down Expand Up @@ -88,7 +93,7 @@ safely cover. This page covers `TypeVarCheck`, the recipe behind
- **Orphans.** A declaration with no uses left (`uses_of` is empty) is orphaned. Conversion skips such names too.

- **Localization.** The origin's declaration is copied as written, with the comment trailing it when it is alone on
its line (`declaration_text`). `DECLARATION_NAME_UNAVAILABLE` if the origin only binds a name its arguments use
its line (`declaration_text`). `NAME_NOT_AT_MODULE_LEVEL` if the origin only binds a name its arguments use
(`declaration_argument_names`) under `if TYPE_CHECKING:` or this file binds it to something else
(`from_import_sources`). All names localized from one import statement are handled together.

Expand All @@ -104,6 +109,9 @@ safely cover. This page covers `TypeVarCheck`, the recipe behind
a statement between it and the last import reads one of its names. Merging and sorting the added imports, and
removing imports that became unused, is left to the CLI's `ruff check --fix --select F401,I001` pass.

- **Line endings.** Files are read with universal newlines and a modified file is written back with LF, also on the
lines the recipe did not change. A file the recipe does not modify is not rewritten.

## Related features

- [Type parameter modernization](../../user/features/type-parameter-modernization.md)
Expand Down Expand Up @@ -142,10 +150,10 @@ See *Verified by test modules* in [Type parameter modernization](../../user/feat

```toml
[project]
requires-python = ">=3.12"
requires-python = ">=3.13"
```

run with `--py 3.11` is treated as below 3.12, so nothing is converted.
run with `--py 3.12` is treated as below 3.13, so a `default=` declaration is not converted.

- Re-exports, namespace packages, `import *` and renamed imports are not followed. In:

Expand Down Expand Up @@ -180,7 +188,7 @@ See *Verified by test modules* in [Type parameter modernization](../../user/feat
report an error.

- Imported names are collected once before the run, so removing a declaration whose importers were all localized
needs a second run. With `origin.py` as above and:
needs a second run, which `--second-pass` does for the files concerned. With `origin.py` as above and:

```python
# main.py
Expand All @@ -204,8 +212,9 @@ See *Verified by test modules* in [Type parameter modernization](../../user/feat

`first` is converted and `T = TypeVar("T")` is removed, although `__all__` exports it.

- A declaration is only treated as public API when `__all__` lists it. One that nothing in the project uses is
removed, even if other projects import it. In:
- A declaration is only treated as public API when `__all__` lists it, since the tool sees nothing outside the scanned
project. One that nothing in the project uses is removed, even if other projects import it. `--ignore` leaves
such a file untouched. In:

```python
# mylib/typing.py
Expand Down Expand Up @@ -311,6 +320,31 @@ See *Verified by test modules* in [Type parameter modernization](../../user/feat

becomes `def first[U = T](items: list[U]) -> U: ...`, whose default still refers to the module-level `T`.

- A constructor written with its module, such as `typing.TypeVar`, is not recognised. This is rare. In:

```python
T = typing.TypeVar("T")

def first(items: list[T]) -> T: ...
```

`first` is left unchanged and the file is reported clean.

- Absolute imports are resolved from the given path, so in a `src/` layout run on the repository root,
`from pkg.typing import T` does not find `src/pkg/typing.py`. This is rare. In:

```python
# src/pkg/typing.py
T = TypeVar("T")

# src/pkg/other.py
from pkg.typing import T

def first(items: list[T]) -> T: ...
```

`T = TypeVar("T")` is removed and `other.py` fails with `ImportError`.

## Need to fix

Errors that should be fixed, not accepted.
Expand All @@ -332,8 +366,3 @@ Errors that should be fixed, not accepted.

`main.py` gets `T = TypeVar("T") # noqa: PLC0105` without the comment above it. The origin keeps its comment
when it drops the declaration.

- Line endings are not preserved: a modified CRLF file is written back with LF. This causes no problem when the
repository stores LF and git converts on checkout, the usual case, since git commits the same content. A
repository that stores CRLF gets a diff on every line of a modified file, so a pull request may be rejected
([issue 279](https://github.com/TNO/Renaissance.Py/issues/279)).
Loading
Loading