Skip to main content

Python/symtable.c (part 4)

Source:

cpython 3.14 @ ab2d84fe1023/Python/symtable.c

This annotation covers PEP 695 type parameter syntax (Python 3.12+). See python_symtable3_detail for comprehension scoping, lambda, class body, and analyze_name.

Map

LinesSymbolRole
1-100symtable_visit_typealiastype X = ... — TypeAlias statement scope
101-220symtable_visit_typeparam[T: int] — TypeVar, ParamSpec, TypeVarTuple scope
221-360Generic function/class scopedef f[T](): ... — type param block
361-400__type_params__Attribute set on generic functions and classes

Reading

symtable_visit_typealias

// CPython: Python/symtable.c:1720 symtable_visit_stmt (TypeAlias_kind)
case TypeAlias_kind:
/* 'type X = int | str' creates a TypeAliasType.
The name X is defined in the enclosing scope.
The value is evaluated lazily in a new TypeParamScope. */
VISIT(st, expr, e->v.TypeAlias.name);
symtable_enter_block(st, e->v.TypeAlias.name, TypeParamBlock, ...);
VISIT(st, expr, e->v.TypeAlias.value);
symtable_exit_block(st);
break;

type Point = tuple[int, int] (PEP 695) creates a TypeAliasType object. The alias value is evaluated lazily in a dedicated scope to support forward references. Point.__value__ triggers evaluation.

Type parameter scope

// CPython: Python/symtable.c:1780 symtable_visit_typeparam
static int
symtable_visit_typeparam(struct symtable *st, typeparam_ty tp)
{
/* Enter a TypeParamBlock for the constraint/bound if present.
The TypeVar name is defined in the enclosing generic scope. */
switch (tp->kind) {
case TypeVar_kind:
/* T or T: int or T: (int, str) */
if (!symtable_add_def(st, tp->v.TypeVar.name, DEF_LOCAL, LOCATION(tp)))
return 0;
if (tp->v.TypeVar.bound) VISIT(st, expr, tp->v.TypeVar.bound);
break;
case ParamSpec_kind:
/* **P */
if (!symtable_add_def(st, tp->v.ParamSpec.name, DEF_LOCAL, LOCATION(tp)))
return 0;
break;
case TypeVarTuple_kind:
/* *Ts */
if (!symtable_add_def(st, tp->v.TypeVarTuple.name, DEF_LOCAL, LOCATION(tp)))
return 0;
break;
}
return 1;
}

Each type parameter (T, *Ts, **P) is a local variable in the generic scope. Constraints and bounds are evaluated in the generic scope so they can reference earlier type params.

Generic function scope

// CPython: Python/symtable.c:1840 symtable_visit_stmt (FunctionDef_kind with type_params)
/* def f[T](x: T) -> T: ...
Compiles as:
1. Enter TypeParamBlock
2. Visit each type_param
3. Enter FunctionBlock for f
4. Visit annotations (which reference T)
5. Exit FunctionBlock
6. Exit TypeParamBlock */

Generic functions have two nested scopes: the type parameter scope (outer) and the function body (inner). The type parameter scope lets the function's annotations reference T without it being a free variable.

gopy notes

symtable_visit_typealias is compile.SymtableVisitTypeAlias in compile/symtable.go. The TypeParamBlock creates its own SymbolTableEntry. Generic function/class compilation is compile.CompileGenericFunction, which emits type param creation opcodes before the function definition.