Skip to content

Implement preliminary PEP 695 support - #5371

Merged
cfbolz merged 30 commits into
py3.12from
py3.12-pep695
Feb 9, 2026
Merged

cfbolz merged 30 commits into
py3.12from
py3.12-pep695

Conversation

@BarrensZeppelin

@BarrensZeppelin BarrensZeppelin commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

This PR contains a first-pass implementation of PEP 695 support (type parameter syntax).
Related to #5273.

For the implementation details and design decisions I used

as references.

This was quite a large task, so I got some help from LLMs. As a result, parts of the implementation are more verbose than they need to be. We can trim it down if necessary.

Known issues:

  • The _pypy_typing module is a hack. To match CPython's behaviour it must probably be rewritten in RPython.
  • The __qualname__ of functions created for annotation scopes are incorrect. I'd rather fix this once the stdlib tests are merged.

I also tried to fix by annotating `positive_lookahead` with
`@specialize.argtype(2)`, but RPython still complained about mixing
numbers and strings, probably because they're wrapped in a tuple
(*args).
codegen.py:
- Extract _visit_type_params_and_collect_names helper method
- Remove duplicate _load_pypy_typing_attr in TypeParamBlockCodeGenerator
- Replace silent fallback with AssertionError for missing _type_params_node

symtable.py:
- Extract _visit_function helper to deduplicate FunctionDef/AsyncFunctionDef
- Replace _visit_type_params with visit_sequence

apptest_pep695.py:
- Remove redundant docstrings
- Use 1/0 and raises(ZeroDivisionError) for lazy evaluation tests
- Consolidate syntax error tests into single test
- Use global raises instead of importing pytest
- Add test_lazy_constraints_evaluation
- Add assertions to test_type_alias_type_subscript
- Import TypeVar, ParamSpec, TypeVarTuple, TypeAliasType, Generic from
  _pypy_typing into typing.py (removes duplicate implementations)
- Add _make_union helper for TypeVar.__or__ forward reference support
- Add _generic_class_getitem and _generic_init_subclass helpers
- Move Generic class to _pypy_typing.py
- Refactor _pypy_typing.py with _BoundVarianceMixin and _LazyEvaluator
- Add missing methods: __mro_entries__, __typing_subst__,
  __typing_prepare_subst__, __parameters__
- Extract _emit_type_alias_type_call helper in codegen.py
Implement CPython's __classdict__ mechanism to allow type parameter bounds
and type alias values to access names defined in enclosing class scopes.

Key changes:
- symtable.py: Track __classdict__ cell in class scopes and mark annotation
  scopes nested in classes as needing classdict access
- codegen.py: Initialize __classdict__ with LOAD_LOCALS at class body start;
  create AnnotationScopeCodeGenerator base class that uses LOAD_FROM_DICT_OR_*
  opcodes to look up names from class dict first
- assemble.py: Add stack effects for LOAD_LOCALS, LOAD_FROM_DICT_OR_GLOBALS,
  and LOAD_FROM_DICT_OR_DEREF
- pyopcode.py: Fix oefmt_name_error call in LOAD_FROM_DICT_OR_GLOBALS

Behavior matches CPython 3.12:
- Class namespace is accessible from annotation scopes directly nested in class
- Functions break the chain (annotation scopes inside functions cannot access
  enclosing class namespaces)
- Class-level names shadow both globals and type parameters when names conflict
- Each class has its own __classdict__; nested classes see only their immediate
  enclosing class
- Add _push_annotation_scope helper for creating annotation scopes with
  classdict handling
- Add _enter_typeparam_scope helper for type parameter scope creation
- Replace is_annotation_scope attribute with isinstance checks
- Apply helpers in _visit_function, visit_ClassDef, visit_TypeAlias,
  and visit_TypeVar
- Add SYM_TYPE_PARAM flag to identify type parameter definitions
- Detect and error on duplicate type parameters (e.g., def f[T, T]())
- Track type parameters through scope analysis via typeparams dict
- Error on nonlocal binding of type parameters
- Disallow lambda and comprehensions in annotation scopes within class
  scope (they would break the __classdict__ lookup mechanism)
- Fix class scope name resolution: class-bound names now resolve to
  globals (via LOAD_FROM_DICT_OR_GLOBALS) instead of via closure, so
  runtime checks __classdict__ then falls back to globals rather than
  the enclosing function's closure
- Add test for conditionally-bound class names with enclosing function
…_scope

- Make needs_classdict a property derived from class_entry
- Simplify _find_enclosing_class_scope to use class_entry propagation
Group related test scenarios into fewer, more focused test functions.
Also fix a bug where TypeVar constraints could not contained starred
expressions.
Annotation scopes should be "invisible" in qualnames per PEP 695.
The fix checks for AnnotationScope before FunctionScope in sub_scope().
Function defaults in generic functions were being evaluated inside the
type params scope, causing them to incorrectly see type parameters.
For example:
    T = 1
    def f[T](x=T): return x
    f()  # Should return 1, was failing

Now defaults are compiled in the outer scope before entering the type
params wrapper, then passed as arguments (.defaults, .kwdefaults) to
the wrapper which loads them via LOAD_FAST.
Generic classes defined with PEP 695 syntax now automatically inherit
from typing.Generic, matching CPython behavior:

  class C[T]: pass
  # C.__bases__ == (Generic,)
  # C.__orig_bases__ == (Generic[T],)

Implementation follows CPython's pattern:
- Register .generic_base in type param scope (symtable)
- Create Generic[T, ...] by subscripting Generic with type params
- Append synthetic .generic_base Name node to bases list
Add check for AnnotationScope before FunctionScope in visit_NamedExpr
so that assignment expressions within comprehensions inside annotation
scopes (e.g., type parameter bounds) raise a SyntaxError.
Explicit `nonlocal` declarations should always add the name to free_vars
for closure propagation, even inside classes.
Comment thread pypy/interpreter/function.py Outdated
@cfbolz
cfbolz merged commit 27035b8 into py3.12 Feb 9, 2026
4 checks passed
@cfbolz

cfbolz commented Feb 9, 2026

Copy link
Copy Markdown
Member

thanks a lot @BarrensZeppelin!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants