8000 [3.13] gh-119933: Improve ``SyntaxError`` message for invalid type pa… · python/cpython@7c47f93 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7c47f93

Browse files
[3.13] gh-119933: Improve SyntaxError message for invalid type parameters expressions (GH-119976) (#120641)
(cherry picked from commit 4bf17c3) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 03b89e3 commit 7c47f93

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
@@ -15,11 +15,23 @@ typedef enum _block_type {
1515
// Used for annotations if 'from __future__ import annotations' is active.
1616
// Annotation blocks cannot bind names and are not evaluated.
1717
AnnotationBlock,
18-
// Used for generics and type aliases. These work mostly like functions
19-
// (see PEP 695 for details). The three different blocks function identically;
20-
// they are different enum entries only so that error messages can be more
21-
// precise.
22-
TypeVarBoundBlock, TypeAliasBlock, TypeParamBlock
18+
19+
// The following blocks are used for generics and type aliases. These work
20+
// mostly like functions (see PEP 695 for details). The three different
21+
// blocks function identically; they are different enum entries only so
22+
// that error messages can be more precise.
23+
24+
// The block to enter when processing a "type" (PEP 695) construction,
25+
// e.g., "type MyGeneric[T] = list[T]".
26+
TypeAliasBlock,
27+
// The block to enter when processing a "generic" (PEP 695) object,
28+
// e.g., "def foo[T](): pass" or "class A[T]: pass".
29+
TypeParametersBlock,
30+
// The block to enter when processing the bound, the constraint tuple
31+
// or the default value of a single "type variable" in the formal sense,
32+
// i.e., a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two
33+
// do not support a bound or a constraint tuple).
34+
TypeVariableBlock,
2335
} _Py_block_ty;
2436

2537
typedef enum _comprehension_type {
@@ -82,7 +94,16 @@ typedef struct _symtable_entry {
8294
PyObject *ste_children; /* list of child blocks */
8395
PyObject *ste_directives;/* locations of global and nonlocal statements */
8496
PyObject *ste_mangled_names; /* set of names for which mangling should be applied */
97+
8598
_Py_block_ty ste_type;
99+
// Optional string set by symtable.c and used when reporting errors.
100+
// The content of that string is a description of the current "context".
101+
//
102+
// For instance, if we are processing the default value of the type
103+
// variable "T" in "def foo[T = int](): pass", `ste_scope_info` is
104+
// set to "a TypeVar default".
105+
const char *ste_scope_info;
106+
86107
int ste_nested; /* true if block is nested */
87108
unsigned ste_free : 1; /* true if block has free variables */
88109
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
@@ -6,8 +6,9 @@
66
LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)
77

88
import weakref
9+
from enum import StrEnum
910

10-
__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]
11+
__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]
1112

1213
def symtable(code, filename, compile_type):
1314
""" Return the toplevel *SymbolTable* for the source code.
@@ -39,6 +40,16 @@ def __call__(self, table, filename):
3940
_newSymbolTable = SymbolTableFactory()
4041

4142

43+
class SymbolTableType(StrEnum):
44+
MODULE = "module"
45+
FUNCTION = "function"
46+
CLASS = "class"
47+
ANNOTATION = "annotation"
48+
TYPE_ALIAS = "type alias"
49+
TYPE_PARAMETERS = "type parameters"
50+
TYPE_VARIABLE = "type variable"
51+
52+
4253
class SymbolTable:
4354

4455
def __init__(self, raw_table, filename):
@@ -62,23 +73,23 @@ def __repr__(self):
6273
def get_type(self):
6374
"""Return the type of the symbol table.
6475
65-
The values returned are 'class', 'module', 'function',
66-
'annotation', 'TypeVar bound', 'type alias', and 'type parameter'.
76+
The value returned is one of the values in
77+
the ``SymbolTableType`` enumeration.
6778
"""
6879
if self._table.type == _symtable.TYPE_MODULE:
69-
return "module"
80+
return SymbolTableType.MODULE
7081
if self._table.type == _symtable.TYPE_FUNCTION:
71-
return "function"
82+
return SymbolTableType.FUNCTION
7283
if self._table.type == _symtable.TYPE_CLASS:
73-
return "class"
84+
return SymbolTableType.CLASS
7485
if self._table.type == _symtable.TYPE_ANNOTATION:
75-
return "annotation"
76-
if self._table.type == _symtable.TYPE_TYPE_VAR_BOUND:
77-
return "TypeVar bound"
86+
return SymbolTableType.ANNOTATION
7887
if self._table.type == _symtable.TYPE_TYPE_ALIAS:
79-
return "type alias"
80-
if self._table.type == _symtable.TYPE_TYPE_PARAM:
81-
return "type parameter"
88+
return SymbolTableType.TYPE_ALIAS
89+
if self._table.type == _symtable.TYPE_TYPE_PARAMETERS:
90+
return SymbolTableType.TYPE_PARAMETERS
91+
if self._table.type == _symtable.TYPE_TYPE_VARIABLE:
92+
return SymbolTableType.TYPE_VARIABLE
8293
assert False, f"unexpected type: {self._table.type}"
8394

8495
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
@@ -88,11 +88,11 @@ symtable_init_constants(PyObject *m)
8888
return -1;
8989
if (PyModule_AddIntConstant(m, "TYPE_ANNOTATION", AnnotationBlock) < 0)
9090
return -1;
91-
if (PyModule_AddIntConstant(m, "TYPE_TYPE_VAR_BOUND", TypeVarBoundBlock) < 0)
92-
return -1;
9391
if (PyModule_AddIntConstant(m, "TYPE_TYPE_ALIAS", TypeAliasBlock) < 0)
9492
return -1;
95-
if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAM", TypeParamBlock) < 0)
93+
if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAMETERS", TypeParametersBlock) < 0)
94+
return -1;
95+
if (PyModule_AddIntConstant(m, "TYPE_TYPE_VARIABLE", TypeVariableBlock) < 0)
9696
return -1;
9797

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

0 commit comments

Comments
 (0)
0