]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-119933: Improve ``SyntaxError`` message for invalid type parameters express...
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Mon, 17 Jun 2024 15:01:49 +0000 (08:01 -0700)
committerGitHub <noreply@github.com>
Mon, 17 Jun 2024 15:01:49 +0000 (08:01 -0700)
(cherry picked from commit 4bf17c381fb7b465f0f26aecb94a6c54cf9be2d3)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Doc/library/symtable.rst
Include/internal/pycore_symtable.h
Lib/symtable.py
Lib/test/test_symtable.py
Lib/test/test_syntax.py
Misc/NEWS.d/next/Core and Builtins/2024-06-03-13-48-44.gh-issue-119933.Kc0HG5.rst [new file with mode: 0644]
Misc/NEWS.d/next/Library/2024-06-05-11-39-21.gh-issue-119933.ooJXQV.rst [new file with mode: 0644]
Modules/symtablemodule.c
Python/symtable.c

index 0480502158433a5a17ee9cb61baaa7ab497ecd15..cc10265ac74a12073271d9e428a989f48268674f 100644 (file)
@@ -31,21 +31,74 @@ Generating Symbol Tables
 Examining Symbol Tables
 -----------------------
 
+.. class:: SymbolTableType
+
+   An enumeration indicating the type of a :class:`SymbolTable` object.
+
+   .. attribute:: MODULE
+      :value: "module"
+
+      Used for the symbol table of a module.
+
+   .. attribute:: FUNCTION
+      :value: "function"
+
+      Used for the symbol table of a function.
+
+   .. attribute:: CLASS
+      :value: "class"
+
+      Used for the symbol table of a class.
+
+   The following members refer to different flavors of
+   :ref:`annotation scopes <annotation-scopes>`.
+
+   .. attribute:: ANNOTATION
+      :value: "annotation"
+
+      Used for annotations if ``from __future__ import annotations`` is active.
+
+   .. attribute:: TYPE_ALIAS
+      :value: "type alias"
+
+      Used for the symbol table of :keyword:`type` constructions.
+
+   .. attribute:: TYPE_PARAMETERS
+      :value: "type parameters"
+
+      Used for the symbol table of :ref:`generic functions <generic-functions>`
+      or :ref:`generic classes <generic-classes>`.
+
+   .. attribute:: TYPE_VARIABLE
+      :value: "type variable"
+
+      Used for the symbol table of the bound, the constraint tuple or the
+      default value of a single type variable in the formal sense, i.e.,
+      a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two do
+      not support a bound or a constraint tuple).
+
+   .. versionadded:: 3.13
+
 .. class:: SymbolTable
 
    A namespace table for a block.  The constructor is not public.
 
    .. method:: get_type()
 
-      Return the type of the symbol table.  Possible values are ``'class'``,
-      ``'module'``, ``'function'``, ``'annotation'``, ``'TypeVar bound'``,
-      ``'type alias'``, and ``'type parameter'``. The latter four refer to
-      different flavors of :ref:`annotation scopes <annotation-scopes>`.
+      Return the type of the symbol table.  Possible values are members
+      of the :class:`SymbolTableType` enumeration.
 
       .. versionchanged:: 3.12
          Added ``'annotation'``,  ``'TypeVar bound'``, ``'type alias'``,
          and ``'type parameter'`` as possible return values.
 
+      .. versionchanged:: 3.13
+         Return values are members of the :class:`SymbolTableType` enumeration.
+
+         The exact values of the returned string may change in the future,
+         and thus, it is recommended to use :class:`SymbolTableType` members
+         instead of hard-coded strings.
+
    .. method:: get_id()
 
       Return the table's identifier.
index ac6c499c08264e82694cc38639443126d2c7f61c..90252bf8365443774f7c40cc523662f50c7e2d39 100644 (file)
@@ -15,11 +15,23 @@ typedef enum _block_type {
     // Used for annotations if 'from __future__ import annotations' is active.
     // Annotation blocks cannot bind names and are not evaluated.
     AnnotationBlock,
-    // Used for generics and type aliases. These work mostly like functions
-    // (see PEP 695 for details). The three different blocks function identically;
-    // they are different enum entries only so that error messages can be more
-    // precise.
-    TypeVarBoundBlock, TypeAliasBlock, TypeParamBlock
+
+    // The following blocks are used for generics and type aliases. These work
+    // mostly like functions (see PEP 695 for details). The three different
+    // blocks function identically; they are different enum entries only so
+    // that error messages can be more precise.
+
+    // The block to enter when processing a "type" (PEP 695) construction,
+    // e.g., "type MyGeneric[T] = list[T]".
+    TypeAliasBlock,
+    // The block to enter when processing a "generic" (PEP 695) object,
+    // e.g., "def foo[T](): pass" or "class A[T]: pass".
+    TypeParametersBlock,
+    // The block to enter when processing the bound, the constraint tuple
+    // or the default value of a single "type variable" in the formal sense,
+    // i.e., a TypeVar, a TypeVarTuple or a ParamSpec object (the latter two
+    // do not support a bound or a constraint tuple).
+    TypeVariableBlock,
 } _Py_block_ty;
 
 typedef enum _comprehension_type {
@@ -82,7 +94,16 @@ typedef struct _symtable_entry {
     PyObject *ste_children;  /* list of child blocks */
     PyObject *ste_directives;/* locations of global and nonlocal statements */
     PyObject *ste_mangled_names; /* set of names for which mangling should be applied */
+
     _Py_block_ty ste_type;
+    // Optional string set by symtable.c and used when reporting errors.
+    // The content of that string is a description of the current "context".
+    //
+    // For instance, if we are processing the default value of the type
+    // variable "T" in "def foo[T = int](): pass", `ste_scope_info` is
+    // set to "a TypeVar default".
+    const char *ste_scope_info;
+
     int ste_nested;      /* true if block is nested */
     unsigned ste_free : 1;        /* true if block has free variables */
     unsigned ste_child_free : 1;  /* true if a child block has free vars,
index 17f820abd566604ca97d5b0fcfe45a9bfc9ef6f7..500a990d96b126f0d12ca6e7437b50ac68af4759 100644 (file)
@@ -6,8 +6,9 @@ from _symtable import (USE, DEF_GLOBAL, DEF_NONLOCAL, DEF_LOCAL, DEF_PARAM,
      LOCAL, GLOBAL_IMPLICIT, GLOBAL_EXPLICIT, CELL)
 
 import weakref
+from enum import StrEnum
 
-__all__ = ["symtable", "SymbolTable", "Class", "Function", "Symbol"]
+__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]
 
 def symtable(code, filename, compile_type):
     """ Return the toplevel *SymbolTable* for the source code.
@@ -39,6 +40,16 @@ class SymbolTableFactory:
 _newSymbolTable = SymbolTableFactory()
 
 
+class SymbolTableType(StrEnum):
+    MODULE = "module"
+    FUNCTION = "function"
+    CLASS = "class"
+    ANNOTATION = "annotation"
+    TYPE_ALIAS = "type alias"
+    TYPE_PARAMETERS = "type parameters"
+    TYPE_VARIABLE = "type variable"
+
+
 class SymbolTable:
 
     def __init__(self, raw_table, filename):
@@ -62,23 +73,23 @@ class SymbolTable:
     def get_type(self):
         """Return the type of the symbol table.
 
-        The values returned are 'class', 'module', 'function',
-        'annotation', 'TypeVar bound', 'type alias', and 'type parameter'.
+        The value returned is one of the values in
+        the ``SymbolTableType`` enumeration.
         """
         if self._table.type == _symtable.TYPE_MODULE:
-            return "module"
+            return SymbolTableType.MODULE
         if self._table.type == _symtable.TYPE_FUNCTION:
-            return "function"
+            return SymbolTableType.FUNCTION
         if self._table.type == _symtable.TYPE_CLASS:
-            return "class"
+            return SymbolTableType.CLASS
         if self._table.type == _symtable.TYPE_ANNOTATION:
-            return "annotation"
-        if self._table.type == _symtable.TYPE_TYPE_VAR_BOUND:
-            return "TypeVar bound"
+            return SymbolTableType.ANNOTATION
         if self._table.type == _symtable.TYPE_TYPE_ALIAS:
-            return "type alias"
-        if self._table.type == _symtable.TYPE_TYPE_PARAM:
-            return "type parameter"
+            return SymbolTableType.TYPE_ALIAS
+        if self._table.type == _symtable.TYPE_TYPE_PARAMETERS:
+            return SymbolTableType.TYPE_PARAMETERS
+        if self._table.type == _symtable.TYPE_TYPE_VARIABLE:
+            return SymbolTableType.TYPE_VARIABLE
         assert False, f"unexpected type: {self._table.type}"
 
     def get_id(self):
index 92b78a8086a83da74520302cdd559212bae92061..ebd609450a898abf2bd7224ab66757a51d9e4f26 100644 (file)
@@ -49,7 +49,7 @@ type GenericAlias[T] = list[T]
 def generic_spam[T](a):
     pass
 
-class GenericMine[T: int]:
+class GenericMine[T: int, U: (int, str) = int]:
     pass
 """
 
@@ -78,6 +78,7 @@ class SymtableTest(unittest.TestCase):
     GenericMine = find_block(top, "GenericMine")
     GenericMine_inner = find_block(GenericMine, "GenericMine")
     T = find_block(GenericMine, "T")
+    U = find_block(GenericMine, "U")
 
     def test_type(self):
         self.assertEqual(self.top.get_type(), "module")
@@ -87,13 +88,14 @@ class SymtableTest(unittest.TestCase):
         self.assertEqual(self.internal.get_type(), "function")
         self.assertEqual(self.foo.get_type(), "function")
         self.assertEqual(self.Alias.get_type(), "type alias")
-        self.assertEqual(self.GenericAlias.get_type(), "type parameter")
+        self.assertEqual(self.GenericAlias.get_type(), "type parameters")
         self.assertEqual(self.GenericAlias_inner.get_type(), "type alias")
-        self.assertEqual(self.generic_spam.get_type(), "type parameter")
+        self.assertEqual(self.generic_spam.get_type(), "type parameters")
         self.assertEqual(self.generic_spam_inner.get_type(), "function")
-        self.assertEqual(self.GenericMine.get_type(), "type parameter")
+        self.assertEqual(self.GenericMine.get_type(), "type parameters")
         self.assertEqual(self.GenericMine_inner.get_type(), "class")
-        self.assertEqual(self.T.get_type(), "TypeVar bound")
+        self.assertEqual(self.T.get_type(), "type variable")
+        self.assertEqual(self.U.get_type(), "type variable")
 
     def test_id(self):
         self.assertGreater(self.top.get_id(), 0)
index 491f7fd7908e97771c6b165bbd8952c77cde3cc1..cdeb26adf34d8987919da264acb7dd1ffaa00f9f 100644 (file)
@@ -2046,16 +2046,91 @@ Invalid expressions in type scopes:
    ...
    SyntaxError: Type parameter list cannot be empty
 
+   >>> def f[T: (x:=3)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar bound
+
+   >>> def f[T: ((x:= 3), int)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar constraint
+
+   >>> def f[T = ((x:=3))](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar default
+
+   >>> async def f[T: (x:=3)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar bound
+
+   >>> async def f[T: ((x:= 3), int)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar constraint
+
+   >>> async def f[T = ((x:=3))](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar default
+
    >>> type A[T: (x:=3)] = int
    Traceback (most recent call last):
       ...
    SyntaxError: named expression cannot be used within a TypeVar bound
 
+   >>> type A[T: ((x:= 3), int)] = int
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar constraint
+
+   >>> type A[T = ((x:=3))] = int
+   Traceback (most recent call last):
+      ...
+   SyntaxError: named expression cannot be used within a TypeVar default
+
+   >>> def f[T: (yield)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar bound
+
+   >>> def f[T: (int, (yield))](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar constraint
+
+   >>> def f[T = (yield)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar default
+
+   >>> def f[*Ts = (yield)](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVarTuple default
+
+   >>> def f[**P = [(yield), int]](): pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a ParamSpec default
+
    >>> type A[T: (yield 3)] = int
    Traceback (most recent call last):
       ...
    SyntaxError: yield expression cannot be used within a TypeVar bound
 
+   >>> type A[T: (int, (yield 3))] = int
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar constraint
+
+   >>> type A[T = (yield 3)] = int
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar default
+
    >>> type A[T: (await 3)] = int
    Traceback (most recent call last):
       ...
@@ -2066,6 +2141,31 @@ Invalid expressions in type scopes:
       ...
    SyntaxError: yield expression cannot be used within a TypeVar bound
 
+   >>> class A[T: (yield 3)]: pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar bound
+
+   >>> class A[T: (int, (yield 3))]: pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar constraint
+
+   >>> class A[T = (yield)]: pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVar default
+
+   >>> class A[*Ts = (yield)]: pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a TypeVarTuple default
+
+   >>> class A[**P = [(yield), int]]: pass
+   Traceback (most recent call last):
+      ...
+   SyntaxError: yield expression cannot be used within a ParamSpec default
+
    >>> type A = (x := 3)
    Traceback (most recent call last):
       ...
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-03-13-48-44.gh-issue-119933.Kc0HG5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-03-13-48-44.gh-issue-119933.Kc0HG5.rst
new file mode 100644 (file)
index 0000000..513a020
--- /dev/null
@@ -0,0 +1,4 @@
+Improve :exc:`SyntaxError` messages for invalid expressions in a type
+parameters bound, a type parameter constraint tuple or a default type
+parameter.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2024-06-05-11-39-21.gh-issue-119933.ooJXQV.rst b/Misc/NEWS.d/next/Library/2024-06-05-11-39-21.gh-issue-119933.ooJXQV.rst
new file mode 100644 (file)
index 0000000..475da88
--- /dev/null
@@ -0,0 +1,3 @@
+Add the :class:`symtable.SymbolTableType` enumeration to represent the
+possible outputs of the :class:`symtable.SymbolTable.get_type` method. Patch
+by Bénédikt Tran.
index b4dbb54c3b47b00cf03c2822ad9f066de1925f46..00e650a9adaaf7394c650495ac15a7afca490141 100644 (file)
@@ -88,11 +88,11 @@ symtable_init_constants(PyObject *m)
         return -1;
     if (PyModule_AddIntConstant(m, "TYPE_ANNOTATION", AnnotationBlock) < 0)
         return -1;
-    if (PyModule_AddIntConstant(m, "TYPE_TYPE_VAR_BOUND", TypeVarBoundBlock) < 0)
-        return -1;
     if (PyModule_AddIntConstant(m, "TYPE_TYPE_ALIAS", TypeAliasBlock) < 0)
         return -1;
-    if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAM", TypeParamBlock) < 0)
+    if (PyModule_AddIntConstant(m, "TYPE_TYPE_PARAMETERS", TypeParametersBlock) < 0)
+        return -1;
+    if (PyModule_AddIntConstant(m, "TYPE_TYPE_VARIABLE", TypeVariableBlock) < 0)
         return -1;
 
     if (PyModule_AddIntMacro(m, LOCAL) < 0) return -1;
index 35c1e4a56629ad2953bcab143d1a9e9b2f0bcc76..65dcf6746d8f69f262ded9ec382369b6e6dbb8ba 100644 (file)
 #define ANNOTATION_NOT_ALLOWED \
 "%s cannot be used within an annotation"
 
-#define TYPEVAR_BOUND_NOT_ALLOWED \
-"%s cannot be used within a TypeVar bound"
+#define EXPR_NOT_ALLOWED_IN_TYPE_VARIABLE \
+"%s cannot be used within %s"
 
-#define TYPEALIAS_NOT_ALLOWED \
+#define EXPR_NOT_ALLOWED_IN_TYPE_ALIAS \
 "%s cannot be used within a type alias"
 
-#define TYPEPARAM_NOT_ALLOWED \
+#define EXPR_NOT_ALLOWED_IN_TYPE_PARAMETERS \
 "%s cannot be used within the definition of a generic"
 
 #define DUPLICATE_TYPE_PARAM \
@@ -106,6 +106,8 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
     ste->ste_mangled_names = NULL;
 
     ste->ste_type = block;
+    ste->ste_scope_info = NULL;
+
     ste->ste_nested = 0;
     ste->ste_free = 0;
     ste->ste_varargs = 0;
@@ -265,9 +267,9 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject* prefix)
         case ClassBlock: blocktype = "ClassBlock"; break;
         case ModuleBlock: blocktype = "ModuleBlock"; break;
         case AnnotationBlock: blocktype = "AnnotationBlock"; break;
-        case TypeVarBoundBlock: blocktype = "TypeVarBoundBlock"; break;
+        case TypeVariableBlock: blocktype = "TypeVariableBlock"; break;
         case TypeAliasBlock: blocktype = "TypeAliasBlock"; break;
-        case TypeParamBlock: blocktype = "TypeParamBlock"; break;
+        case TypeParametersBlock: blocktype = "TypeParametersBlock"; break;
     }
     const char *comptype = "";
     switch (ste->ste_comprehension) {
@@ -525,9 +527,9 @@ int
 _PyST_IsFunctionLike(PySTEntryObject *ste)
 {
     return ste->ste_type == FunctionBlock
-        || ste->ste_type == TypeVarBoundBlock
+        || ste->ste_type == TypeVariableBlock
         || ste->ste_type == TypeAliasBlock
-        || ste->ste_type == TypeParamBlock;
+        || ste->ste_type == TypeParametersBlock;
 }
 
 static int
@@ -1494,7 +1496,7 @@ symtable_enter_type_param_block(struct symtable *st, identifier name,
                                int end_lineno, int end_col_offset)
 {
     _Py_block_ty current_type = st->st_cur->ste_type;
-    if(!symtable_enter_block(st, name, TypeParamBlock, ast, lineno,
+    if(!symtable_enter_block(st, name, TypeParametersBlock, ast, lineno,
                              col_offset, end_lineno, end_col_offset)) {
         return 0;
     }
@@ -2069,20 +2071,20 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
         }
         /* Disallow usage in ClassBlock and type scopes */
         if (ste->ste_type == ClassBlock ||
-            ste->ste_type == TypeParamBlock ||
+            ste->ste_type == TypeParametersBlock ||
             ste->ste_type == TypeAliasBlock ||
-            ste->ste_type == TypeVarBoundBlock) {
+            ste->ste_type == TypeVariableBlock) {
             switch (ste->ste_type) {
                 case ClassBlock:
                     PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_IN_CLASS);
                     break;
-                case TypeParamBlock:
+                case TypeParametersBlock:
                     PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_IN_TYPEPARAM);
                     break;
                 case TypeAliasBlock:
                     PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_IN_TYPEALIAS);
                     break;
-                case TypeVarBoundBlock:
+                case TypeVariableBlock:
                     PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_IN_TYPEVAR_BOUND);
                     break;
                 default:
@@ -2288,19 +2290,27 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
 }
 
 static int
-symtable_visit_type_param_bound_or_default(struct symtable *st, expr_ty e, identifier name, void *key)
+symtable_visit_type_param_bound_or_default(
+    struct symtable *st, expr_ty e, identifier name,
+    void *key, const char *ste_scope_info)
 {
     if (e) {
         int is_in_class = st->st_cur->ste_can_see_class_scope;
-        if (!symtable_enter_block(st, name, TypeVarBoundBlock, key, LOCATION(e)))
+        if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e)))
             return 0;
+
         st->st_cur->ste_can_see_class_scope = is_in_class;
         if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(e))) {
             VISIT_QUIT(st, 0);
         }
+
+        assert(ste_scope_info != NULL);
+        st->st_cur->ste_scope_info = ste_scope_info;
         VISIT(st, expr, e);
-        if (!symtable_exit_block(st))
+
+        if (!symtable_exit_block(st)) {
             return 0;
+        }
     }
     return 1;
 }
@@ -2318,6 +2328,12 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
         if (!symtable_add_def(st, tp->v.TypeVar.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp)))
             VISIT_QUIT(st, 0);
 
+        const char *ste_scope_info = NULL;
+        const expr_ty bound = tp->v.TypeVar.bound;
+        if (bound != NULL) {
+            ste_scope_info = bound->kind == Tuple_kind ? "a TypeVar constraint" : "a TypeVar bound";
+        }
+
         // We must use a different key for the bound and default. The obvious choice would be to
         // use the .bound and .default_value pointers, but that fails when the expression immediately
         // inside the bound or default is a comprehension: we would reuse the same key for
@@ -2325,11 +2341,12 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
         // The only requirement for the key is that it is unique and it matches the logic in
         // compile.c where the scope is retrieved.
         if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVar.bound, tp->v.TypeVar.name,
-                                                        (void *)tp)) {
+                                                        (void *)tp, ste_scope_info)) {
             VISIT_QUIT(st, 0);
         }
+
         if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVar.default_value, tp->v.TypeVar.name,
-                                                        (void *)((uintptr_t)tp + 1))) {
+                                                        (void *)((uintptr_t)tp + 1), "a TypeVar default")) {
             VISIT_QUIT(st, 0);
         }
         break;
@@ -2337,8 +2354,9 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
         if (!symtable_add_def(st, tp->v.TypeVarTuple.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp))) {
             VISIT_QUIT(st, 0);
         }
+
         if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVarTuple.default_value, tp->v.TypeVarTuple.name,
-                                                        (void *)tp)) {
+                                                        (void *)tp, "a TypeVarTuple default")) {
             VISIT_QUIT(st, 0);
         }
         break;
@@ -2346,8 +2364,9 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
         if (!symtable_add_def(st, tp->v.ParamSpec.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp))) {
             VISIT_QUIT(st, 0);
         }
+
         if (!symtable_visit_type_param_bound_or_default(st, tp->v.ParamSpec.default_value, tp->v.ParamSpec.name,
-                                                        (void *)tp)) {
+                                                        (void *)tp, "a ParamSpec default")) {
             VISIT_QUIT(st, 0);
         }
         break;
@@ -2729,12 +2748,21 @@ symtable_raise_if_annotation_block(struct symtable *st, const char *name, expr_t
     enum _block_type type = st->st_cur->ste_type;
     if (type == AnnotationBlock)
         PyErr_Format(PyExc_SyntaxError, ANNOTATION_NOT_ALLOWED, name);
-    else if (type == TypeVarBoundBlock)
-        PyErr_Format(PyExc_SyntaxError, TYPEVAR_BOUND_NOT_ALLOWED, name);
-    else if (type == TypeAliasBlock)
-        PyErr_Format(PyExc_SyntaxError, TYPEALIAS_NOT_ALLOWED, name);
-    else if (type == TypeParamBlock)
-        PyErr_Format(PyExc_SyntaxError, TYPEPARAM_NOT_ALLOWED, name);
+    else if (type == TypeVariableBlock) {
+        const char *info = st->st_cur->ste_scope_info;
+        assert(info != NULL); // e.g., info == "a ParamSpec default"
+        PyErr_Format(PyExc_SyntaxError, EXPR_NOT_ALLOWED_IN_TYPE_VARIABLE, name, info);
+    }
+    else if (type == TypeAliasBlock) {
+        // for now, we do not have any extra information
+        assert(st->st_cur->ste_scope_info == NULL);
+        PyErr_Format(PyExc_SyntaxError, EXPR_NOT_ALLOWED_IN_TYPE_ALIAS, name);
+    }
+    else if (type == TypeParametersBlock) {
+        // for now, we do not have any extra information
+        assert(st->st_cur->ste_scope_info == NULL);
+        PyErr_Format(PyExc_SyntaxError, EXPR_NOT_ALLOWED_IN_TYPE_PARAMETERS, name);
+    }
     else
         return 1;