Skip to content

Commit 4bf17c3

Browse files
gh-119933: Improve SyntaxError message for invalid type parameters expressions (#119976)
Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 274f844 commit 4bf17c3

File tree

9 files changed

+277
-55
lines changed

9 files changed

+277
-55
lines changed

Doc/library/symtable.rst

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,74 @@ Generating Symbol Tables
3131
Examining Symbol Tables
3232
-----------------------
3333

34+
.. class:: SymbolTableType
35+
36+
An enumeration indicating the type of a :class:`SymbolTable` object.
37+
38+
.. attribute:: MODULE
39+
:value: "module"
40+
41+
Used for the symbol table of a module.
42+
43+
.. attribute:: FUNCTION
44+
:value: "function"
45+
46+
Used for the symbol table of a function.
47+
48+
.. attribute:: CLASS
49+
:value: "class"
50+
51+
Used for the symbol table of a class.
52+
53+
The following members refer to different flavors of
54+
:ref:`annotation scopes <annotation-scopes>`.
55+
56+
.. attribute:: ANNOTATION
57+
:value: "annotation"
58+
59+
Used for annotations if ``from __future__ import annotations`` is active.
60+
61+
.. attribute:: TYPE_ALIAS
62+
:value: "type alias"
63+
64+
Used for the symbol table of :keyword:`type` constructions.
65+
66+
.. attribute:: TYPE_PARAMETERS
67+
:value: "type parameters"
68+
69+
Used for the symbol table of :ref:`generic functions <generic-functions>`
70+
or :ref:`generic classes <generic-classes>`.
71+
72+
.. attribute:: TYPE_VARIABLE
73+
:value: "type variable"
74+
75+
Used for the symbol table of the bound, the constraint tuple or the
76+
default value of a single type variable in the formal sense, i.e.,
77+
a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two do
78+
not support a bound or a constraint tuple).
79+
80+
.. versionadded:: 3.13
81+
3482
.. class:: SymbolTable
3583

3684
A namespace table for a block. The constructor is not public.
3785

3886
.. method:: get_type()
3987

40-
Return the type of the symbol table. Possible values are ``'class'``,
41-
``'module'``, ``'function'``, ``'annotation'``, ``'TypeVar bound'``,
42-
``'type alias'``, and ``'type parameter'``. The latter four refer to
43-
different flavors of :ref:`annotation scopes <annotation-scopes>`.
88+
Return the type of the symbol table. Possible values are members
89+
of the :class:`SymbolTableType` enumeration.
4490

4591
.. versionchanged:: 3.12
4692
Added ``'annotation'``, ``'TypeVar bound'``, ``'type alias'``,
4793
and ``'type parameter'`` as possible return values.
4894

95+
.. versionchanged:: 3.13
96+
Return values are members of the :class:`SymbolTableType` enumeration.
97+
98+
The exact values of the returned string may change in the future,
99+
and thus, it is recommended to use :class:`SymbolTableType` members
100+
instead of hard-coded strings.
101+
49102
.. method:: get_id()
50103

51104
Return the table's identifier.

Include/internal/pycore_symtable.h

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,23 @@ typedef enum _block_type {
1616
// annotation blocks cannot bind names and are not evaluated. Otherwise, they
1717
// are lazily evaluated (see PEP 649).
1818
AnnotationBlock,
19-
// Used for generics and type aliases. These work mostly like functions
20-
// (see PEP 695 for details). The three different blocks function identically;
21-
// they are different enum entries only so that error messages can be more
22-
// precise.
23-
TypeVarBoundBlock, TypeAliasBlock, TypeParamBlock
19+
20+
// The following blocks are used for generics and type aliases. These work
21+
// mostly like functions (see PEP 695 for details). The three different
22+
// blocks function identically; they are different enum entries only so
23+
// that error messages can be more precise.
24+
25+
// The block to enter when processing a "type" (PEP 695) construction,
26+
// e.g., "type MyGeneric[T] = list[T]".
27+
TypeAliasBlock,
28+
// The block to enter when processing a "generic" (PEP 695) object,
29+
// e.g., "def foo[T](): pass" or "class A[T]: pass".
30+
TypeParametersBlock,
31+
// The block to enter when processing the bound, the constraint tuple
32+
// or the default value of a single "type variable" in the formal sense,
33+
// i.e., a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two
34+
// do not support a bound or a constraint tuple).
35+
TypeVariableBlock,
2436
} _Py_block_ty;
2537

2638
typedef enum _comprehension_type {
@@ -83,7 +95,16 @@ typedef struct _symtable_entry {
8395
PyObject *ste_children; /* list of child blocks */
8496
PyObject *ste_directives;/* locations of global and nonlocal statements */
8597
PyObject *ste_mangled_names; /* set of names for which mangling should be applied */
98+
8699
_Py_block_ty ste_type;
100+
// Optional string set by symtable.c and used when reporting errors.
101+
// The content of that string is a description of the current "context".
102+
//
103+
// For instance, if we are processing the default value of the type
104+
// variable "T" in "def foo[T = int](): pass", `ste_scope_info` is
105+
// set to "a TypeVar default".
106+
const char *ste_scope_info;
107+
87108
int ste_nested; /* true if block is nested */
88109
unsigned ste_free : 1; /* true if block has free variables */
89110
unsigned ste_child_free : 1; /* true if a child block has free vars,

Lib/symtable.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
)
1414

1515
import weakref
16+
from enum import StrEnum
1617

17-
__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]
18+
__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]
1819

1920
def symtable(code, filename, compile_type):
2021
""" Return the toplevel *SymbolTable* for the source code.
@@ -46,6 +47,16 @@ def __call__(self, table, filename):
4647
_newSymbolTable = SymbolTableFactory()
4748

4849

50+
class SymbolTableType(StrEnum):
51+
MODULE = "module"
52+
FUNCTION = "function"
53+
CLASS = "class"
54+
ANNOTATION = "annotation"
55+
TYPE_ALIAS = "type alias"
56+
TYPE_PARAMETERS = "type parameters"
57+
TYPE_VARIABLE = "type variable"
58+
59+
4960
class SymbolTable:
5061

5162
def __init__(self, raw_table, filename):
@@ -69,23 +80,23 @@ def __repr__(self):
6980
def get_type(self):
7081
"""Return the type of the symbol table.
7182
72-
The values returned are 'class', 'module', 'function',
73-
'annotation', 'TypeVar bound', 'type alias', and 'type parameter'.
83+
The value returned is one of the values in
84+
the ``SymbolTableType`` enumeration.
7485
"""
7586
if self._table.type == _symtable.TYPE_MODULE:
76-
return "module"
87+
return SymbolTableType.MODULE
7788
if self._table.type == _symtable.TYPE_FUNCTION:
78-
return "function"
89+
return SymbolTableType.FUNCTION
7990
if self._table.type == _symtable.TYPE_CLASS:
80-
return "class"
91+
return SymbolTableType.CLASS
8192
if self._table.type == _symtable.TYPE_ANNOTATION:
82-
return "annotation"
83-
if self._table.type == _symtable.TYPE_TYPE_VAR_BOUND:
84-
return "TypeVar bound"
93+
return SymbolTableType.ANNOTATION
8594
if self._table.type == _symtable.TYPE_TYPE_ALIAS:
86-
return "type alias"
87-
if self._table.type == _symtable.TYPE_TYPE_PARAM:
88-
return "type parameter"
95+
return SymbolTableType.TYPE_ALIAS
96+
if self._table.type == _symtable.TYPE_TYPE_PARAMETERS:
97+
return SymbolTableType.TYPE_PARAMETERS
98+
if self._table.type == _symtable.TYPE_TYPE_VARIABLE:
99+
return SymbolTableType.TYPE_VARIABLE
89100
assert False, f"unexpected type: {self._table.type}"
90101

91102
def get_id(self):

Lib/test/test_symtable.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def namespace_test(): pass
4949
def generic_spam[T](a):
5050
pass
5151
52-
class GenericMine[T: int]:
52+
class GenericMine[T: int, U: (int, str) = int]:
5353
pass
5454
"""
5555

@@ -78,6 +78,7 @@ class SymtableTest(unittest.TestCase):
7878
GenericMine = find_block(top, "GenericMine")
7979
GenericMine_inner = find_block(GenericMine, "GenericMine")
8080
T = find_block(GenericMine, "T")
81+
U = find_block(GenericMine, "U")
8182

8283
def test_type(self):
8384
self.assertEqual(self.top.get_type(), "module")
@@ -87,13 +88,14 @@ def test_type(self):
8788
self.assertEqual(self.internal.get_type(), "function")
8889
self.assertEqual(self.foo.get_type(), "function")
8990
self.assertEqual(self.Alias.get_type(), "type alias")
90-
self.assertEqual(self.GenericAlias.get_type(), "type parameter")
91+
self.assertEqual(self.GenericAlias.get_type(), "type parameters")
9192
self.assertEqual(self.GenericAlias_inner.get_type(), "type alias")
92-
self.assertEqual(self.generic_spam.get_type(), "type parameter")
93+
self.assertEqual(self.generic_spam.get_type(), "type parameters")
9394
self.assertEqual(self.generic_spam_inner.get_type(), "function")
94-
self.assertEqual(self.GenericMine.get_type(), "type parameter")
95+
self.assertEqual(self.GenericMine.get_type(), "type parameters")
9596
self.assertEqual(self.GenericMine_inner.get_type(), "class")
96-
self.assertEqual(self.T.get_type(), "TypeVar bound")
97+
self.assertEqual(self.T.get_type(), "type variable")
98+
self.assertEqual(self.U.get_type(), "type variable")
9799

98100
def test_id(self):
99101
self.assertGreater(self.top.get_id(), 0)

Lib/test/test_syntax.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,16 +2046,91 @@ def f(x: *b)
20462046
...
20472047
SyntaxError: Type parameter list cannot be empty
20482048
2049+
>>> def f[T: (x:=3)](): pass
2050+
Traceback (most recent call last):
2051+
...
2052+
SyntaxError: named expression cannot be used within a TypeVar bound
2053+
2054+
>>> def f[T: ((x:= 3), int)](): pass
2055+
Traceback (most recent call last):
2056+
...
2057+
SyntaxError: named expression cannot be used within a TypeVar constraint
2058+
2059+
>>> def f[T = ((x:=3))](): pass
2060+
Traceback (most recent call last):
2061+
...
2062+
SyntaxError: named expression cannot be used within a TypeVar default
2063+
2064+
>>> async def f[T: (x:=3)](): pass
2065+
Traceback (most recent call last):
2066+
...
2067+
SyntaxError: named expression cannot be used within a TypeVar bound
2068+
2069+
>>> async def f[T: ((x:= 3), int)](): pass
2070+
Traceback (most recent call last):
2071+
...
2072+
SyntaxError: named expression cannot be used within a TypeVar constraint
2073+
2074+
>>> async def f[T = ((x:=3))](): pass
2075+
Traceback (most recent call last):
2076+
...
2077+
SyntaxError: named expression cannot be used within a TypeVar default
2078+
20492079
>>> type A[T: (x:=3)] = int
20502080
Traceback (most recent call last):
20512081
...
20522082
SyntaxError: named expression cannot be used within a TypeVar bound
20532083
2084+
>>> type A[T: ((x:= 3), int)] = int
2085+
Traceback (most recent call last):
2086+
...
2087+
SyntaxError: named expression cannot be used within a TypeVar constraint
2088+
2089+
>>> type A[T = ((x:=3))] = int
2090+
Traceback (most recent call last):
2091+
...
2092+
SyntaxError: named expression cannot be used within a TypeVar default
2093+
2094+
>>> def f[T: (yield)](): pass
2095+
Traceback (most recent call last):
2096+
...
2097+
SyntaxError: yield expression cannot be used within a TypeVar bound
2098+
2099+
>>> def f[T: (int, (yield))](): pass
2100+
Traceback (most recent call last):
2101+
...
2102+
SyntaxError: yield expression cannot be used within a TypeVar constraint
2103+
2104+
>>> def f[T = (yield)](): pass
2105+
Traceback (most recent call last):
2106+
...
2107+
SyntaxError: yield expression cannot be used within a TypeVar default
2108+
2109+
>>> def f[*Ts = (yield)](): pass
2110+
Traceback (most recent call last):
2111+
...
2112+
SyntaxError: yield expression cannot be used within a TypeVarTuple default
2113+
2114+
>>> def f[**P = [(yield), int]](): pass
2115+
Traceback (most recent call last):
2116+
...
2117+
SyntaxError: yield expression cannot be used within a ParamSpec default
2118+
20542119
>>> type A[T: (yield 3)] = int
20552120
Traceback (most recent call last):
20562121
...
20572122
SyntaxError: yield expression cannot be used within a TypeVar bound
20582123
2124+
>>> type A[T: (int, (yield 3))] = int
2125+
Traceback (most recent call last):
2126+
...
2127+
SyntaxError: yield expression cannot be used within a TypeVar constraint
2128+
2129+
>>> type A[T = (yield 3)] = int
2130+
Traceback (most recent call last):
2131+
...
2132+
SyntaxError: yield expression cannot be used within a TypeVar default
2133+
20592134
>>> type A[T: (await 3)] = int
20602135
Traceback (most recent call last):
20612136
...
@@ -2066,6 +2141,31 @@ def f(x: *b)
20662141
...
20672142
SyntaxError: yield expression cannot be used within a TypeVar bound
20682143
2144+
>>> class A[T: (yield 3)]: pass
2145+
Traceback (most recent call last):
2146+
...
2147+
SyntaxError: yield expression cannot be used within a TypeVar bound
2148+
2149+
>>> class A[T: (int, (yield 3))]: pass
2150+
Traceback (most recent call last):
2151+
...
2152+
SyntaxError: yield expression cannot be used within a TypeVar constraint
2153+
2154+
>>> class A[T = (yield)]: pass
2155+
Traceback (most recent call last):
2156+
...
2157+
SyntaxError: yield expression cannot be used within a TypeVar default
2158+
2159+
>>> class A[*Ts = (yield)]: pass
2160+
Traceback (most recent call last):
2161+
...
2162+
SyntaxError: yield expression cannot be used within a TypeVarTuple default
2163+
2164+
>>> class A[**P = [(yield), int]]: pass
2165+
Traceback (most recent call last):
2166+
...
2167+
SyntaxError: yield expression cannot be used within a ParamSpec default
2168+
20692169
>>> type A = (x := 3)
20702170
Traceback (most recent call last):
20712171
...
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve :exc:`SyntaxError` messages for invalid expressions in a type
2+
parameters bound, a type parameter constraint tuple or a default type
3+
parameter.
4+
Patch by Bénédikt Tran.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add the :class:`symtable.SymbolTableType` enumeration to represent the
2+
possible outputs of the :class:`symtable.SymbolTable.get_type` method. Patch
3+
by Bénédikt Tran.

Modules/symtablemodule.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ symtable_init_constants(PyObject *m)
9191
return -1;
9292
if (PyModule_AddIntConstant(m, "TYPE_ANNOTATION", AnnotationBlock) < 0)
9393
return -1;
94-
if (PyModule_AddIntConstant(m, "TYPE_TYPE_VAR_BOUND", TypeVarBoundBlock) < 0)
95-
return -1;
9694
if (PyModule_AddIntConstant(m, "TYPE_TYPE_ALIAS", TypeAliasBlock) < 0)
9795
return -1;
98-
if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAM", TypeParamBlock) < 0)
96+
if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAMETERS", TypeParametersBlock) < 0)
97+
return -1;
98+
if (PyModule_AddIntConstant(m, "TYPE_TYPE_VARIABLE", TypeVariableBlock) < 0)
9999
return -1;
100100

101101
if (PyModule_AddIntMacro(m, LOCAL) < 0) return -1;

0 commit comments

Comments
 (0)