better typing
This commit is contained in:
@@ -73,6 +73,20 @@ Use these defaults unless a hard compatibility constraint prevents them:
|
|||||||
5. Use `typing.Literal` when a finite value set is the real contract.
|
5. Use `typing.Literal` when a finite value set is the real contract.
|
||||||
6. Remove legacy typing aliases and module-level `TypeVar` declarations when PEP 695 can replace them.
|
6. Remove legacy typing aliases and module-level `TypeVar` declarations when PEP 695 can replace them.
|
||||||
7. Keep runtime behavior unchanged unless the task explicitly requests behavior refactors.
|
7. Keep runtime behavior unchanged unless the task explicitly requests behavior refactors.
|
||||||
|
8. Treat `typing.cast(...)` as a last resort, not a default fix for type-checker complaints.
|
||||||
|
9. Before adding a cast, prefer real narrowing (`isinstance`, `TypeIs`/`TypeGuard`), explicit control-flow checks, or small annotation refactors that preserve behavior.
|
||||||
|
10. Reject casts whose only purpose is to silence the checker without a clear runtime invariant.
|
||||||
|
11. For closed variant sets (`Literal`/`Enum`/tagged unions), prefer structural pattern matching with exhaustiveness checks (`assert_never`) for deterministic narrowing.
|
||||||
|
|
||||||
|
## Cast Discipline
|
||||||
|
|
||||||
|
Use this policy whenever a modernization pass encounters a potential cast:
|
||||||
|
|
||||||
|
1. Confirm whether the checker can be satisfied with stronger narrowing first (for example `isinstance` or assertion-based narrowing).
|
||||||
|
2. If a cast is still necessary, keep it narrowly scoped to the exact expression rather than widening an entire variable flow.
|
||||||
|
3. Document the invariant that makes the cast valid in human terms, not just "type checker requires this".
|
||||||
|
4. Prefer fixing imprecise annotations at the source over stacking repeated casts downstream.
|
||||||
|
5. If multiple casts appear in one code path, treat that as a design smell and propose a structural typing fix.
|
||||||
|
|
||||||
## Completion Checks
|
## Completion Checks
|
||||||
|
|
||||||
@@ -81,6 +95,7 @@ Use these defaults unless a hard compatibility constraint prevents them:
|
|||||||
3. Public APIs are unchanged unless explicitly requested.
|
3. Public APIs are unchanged unless explicitly requested.
|
||||||
4. Feature-level recommendations include source links.
|
4. Feature-level recommendations include source links.
|
||||||
5. Any deferral is backed by a specific hard constraint (for example Python version floor).
|
5. Any deferral is backed by a specific hard constraint (for example Python version floor).
|
||||||
|
6. New casts, if any, are minimal, justified by an explicit invariant, and not used as checker-silencing shortcuts.
|
||||||
|
|
||||||
## Output Contract
|
## Output Contract
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ Use this page as the canonical source index when making typing modernization rec
|
|||||||
- [Typing module documentation](https://docs.python.org/3/library/typing.html)
|
- [Typing module documentation](https://docs.python.org/3/library/typing.html)
|
||||||
- [Typing specification (typing.python.org)](https://typing.python.org/)
|
- [Typing specification (typing.python.org)](https://typing.python.org/)
|
||||||
- [Built-in types and generic aliases](https://docs.python.org/3/library/stdtypes.html)
|
- [Built-in types and generic aliases](https://docs.python.org/3/library/stdtypes.html)
|
||||||
|
- [Python language reference: `match` statement](https://docs.python.org/3/reference/compound_stmts.html#the-match-statement)
|
||||||
|
- [PEP 634: Structural Pattern Matching specification](https://peps.python.org/pep-0634/)
|
||||||
|
- [typing.cast reference (runtime no-op)](https://docs.python.org/3/library/typing.html#typing.cast)
|
||||||
|
- [Typing spec directives for `cast()`](https://typing.python.org/en/latest/spec/directives.html#cast)
|
||||||
|
- [Mypy type narrowing and casts guidance](https://mypy.readthedocs.io/en/stable/type_narrowing.html#casts)
|
||||||
|
- [Typing guide: exhaustiveness and `assert_never`](https://typing.python.org/en/latest/guides/unreachable.html#assert-never-and-exhaustiveness-checking)
|
||||||
|
- [Mypy: `Literal`/`Enum` exhaustiveness with `match`](https://mypy.readthedocs.io/en/stable/literal_types.html#exhaustiveness-checking)
|
||||||
|
|
||||||
## Tooling References
|
## Tooling References
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,46 @@ This workflow is distilled from practical typing modernization passes and is des
|
|||||||
- If a stricter type (for example `Literal`) may reject existing runtime inputs:
|
- If a stricter type (for example `Literal`) may reject existing runtime inputs:
|
||||||
- apply only when the input contract is already finite; otherwise defer with a contract-change note.
|
- apply only when the input contract is already finite; otherwise defer with a contract-change note.
|
||||||
|
|
||||||
|
## Deterministic Narrowing with `match`
|
||||||
|
|
||||||
|
Use structural pattern matching when the domain is a closed set (for example tagged unions, enum dispatch, or finite literal variants).
|
||||||
|
|
||||||
|
1. Prefer `match` over long `if`/`elif` ladders when each branch represents a distinct variant.
|
||||||
|
2. For tagged unions, match the discriminant and extract payload fields in the same case.
|
||||||
|
3. Add a default `case _:` branch with `assert_never(...)` to enforce exhaustiveness in static analysis.
|
||||||
|
4. Keep patterns explicit and side-effect-light; avoid relying on bindings from failed matches.
|
||||||
|
|
||||||
|
Example with a tagged `TypedDict` union:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Literal, TypedDict, assert_never
|
||||||
|
|
||||||
|
|
||||||
|
class NewJobEvent(TypedDict):
|
||||||
|
tag: Literal["new-job"]
|
||||||
|
job_name: str
|
||||||
|
|
||||||
|
|
||||||
|
class CancelJobEvent(TypedDict):
|
||||||
|
tag: Literal["cancel-job"]
|
||||||
|
job_id: int
|
||||||
|
|
||||||
|
|
||||||
|
type Event = NewJobEvent | CancelJobEvent
|
||||||
|
|
||||||
|
|
||||||
|
def route(event: Event) -> str:
|
||||||
|
match event:
|
||||||
|
case {"tag": "new-job", "job_name": job_name}:
|
||||||
|
return f"enqueue:{job_name}"
|
||||||
|
case {"tag": "cancel-job", "job_id": job_id}:
|
||||||
|
return f"cancel:{job_id}"
|
||||||
|
case _:
|
||||||
|
assert_never(event)
|
||||||
|
```
|
||||||
|
|
||||||
|
This pattern makes narrowing deterministic per branch and surfaces missing variants as type-checker errors during review.
|
||||||
|
|
||||||
## Quality Criteria
|
## Quality Criteria
|
||||||
|
|
||||||
1. All edits are syntax-valid for the target Python versions.
|
1. All edits are syntax-valid for the target Python versions.
|
||||||
|
|||||||
Reference in New Issue
Block a user