Skip to content

gh-119933 : Improve SyntaxError message for invalid type parameters expressions #119976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6279686
fix ``SyntaxError`` for invalid type parameters expressions
picnixz Jun 3, 2024
7075bc6
blurb
picnixz Jun 3, 2024
74d330e
improve doc & naming
picnixz Jun 3, 2024
1ff9183
visual improvements of some test
picnixz Jun 3, 2024
234200c
Merge branch 'main' into fix-119933
picnixz Jun 3, 2024
fa7f02f
simplify the flow
picnixz Jun 3, 2024
b77cebb
Merge branch 'main' into fix-119933
picnixz Jun 4, 2024
7482db6
address review
picnixz Jun 4, 2024
2edf665
address review
picnixz Jun 4, 2024
845c7a6
use the same declaration order across files
picnixz Jun 4, 2024
bbfdcb7
Use enumeration members instead of `.value`.
picnixz Jun 5, 2024
f1208c7
export `SymbolTableType` enumeration
picnixz Jun 5, 2024
e533e4d
update NEWS
picnixz Jun 5, 2024
4cf0bf8
simplify `ste_scope_info` creation
picnixz Jun 5, 2024
740c8f2
improve documentation for `TypeVariableBlock`
picnixz Jun 5, 2024
3e329df
update documentation
picnixz Jun 5, 2024
18bc1f3
fixup
picnixz Jun 5, 2024
dd8d461
Merge branch 'main' into fix-119933
picnixz Jun 5, 2024
2ca6fd9
blurb
picnixz Jun 5, 2024
278d1fc
fixup
picnixz Jun 5, 2024
3f6a03e
Merge branch 'main' into fix-119933
picnixz Jun 12, 2024
3667349
Merge branch 'main' into fix-119933
picnixz Jun 12, 2024
82a37b6
Move SyntaxError's NEWS into "Core and Builtins"
picnixz Jun 13, 2024
cf45f40
improve documentation wording
picnixz Jun 13, 2024
e96f6b4
Update Python/symtable.c
picnixz Jun 17, 2024
705a76f
Update Doc/library/symtable.rst
picnixz Jun 17, 2024
abad81b
address review
picnixz Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ typedef enum _comprehension_type {
SetComprehension = 3,
GeneratorExpression = 4 } _Py_comprehension_ty;

/* Additional flags that are set temporarily on a ``_symtable_entry`` object.
*
* Those flags are only used to add some information on the current context.
*/
typedef enum _extra_flags_e {
// Mutually exclusive flags indicating the kind of type variable
// being processed. Use this flag to avoid passing or checking
// twice the kind of a ``type_param_ty`` object.
InTypeVar = 1,
InTypeVarTuple = 2,
InParamSpec = (1 << 2),

// Mutually exclusive flags indicating which component of
// a type parameters block is being processed. Those flags
// are used in conjunction with 'InTypeVar', 'InTypeVarTuple'
// or 'InParamSpec' but not all combinations are supported.
InTypeParamBound = (1 << 3),
InTypeParamConstraint = (1 << 4),
InTypeParamDefault = (1 << 5),
} _extra_flags_t;

#define CLEAR_TYPE_PARAM_KIND_EXTRA_FLAGS ~(InTypeVar | InTypeVarTuple | InParamSpec)
#define CLEAR_TYPE_PARAM_ATTR_EXTRA_FLAGS ~(InTypeParamBound | InTypeParamConstraint | InTypeParamDefault)

/* source location information */
typedef struct {
int lineno;
Expand Down Expand Up @@ -83,6 +107,7 @@ typedef struct _symtable_entry {
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;
_extra_flags_t ste_extra_flags; /* extra temporary flags */
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,
Expand Down
100 changes: 100 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -2068,16 +2068,91 @@ def f(x: *b)
...
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):
...
Expand All @@ -2088,6 +2163,31 @@ def f(x: *b)
...
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):
...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve :exc:`SyntaxError` messages for :pep:`695` and :pep:`696`.
52 changes: 43 additions & 9 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@
#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_PARAM_BLOCK \
"%s cannot be used within %s%s"

#define TYPEALIAS_NOT_ALLOWED \
"%s cannot be used within a type alias"
Expand Down Expand Up @@ -106,6 +106,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
ste->ste_mangled_names = NULL;

ste->ste_type = block;
ste->ste_extra_flags = 0;
ste->ste_nested = 0;
ste->ste_free = 0;
ste->ste_varargs = 0;
Expand Down Expand Up @@ -1339,6 +1340,8 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block,
*/
if (prev) {
ste->ste_comp_iter_expr = prev->ste_comp_iter_expr;
/* inherit already set flags */
ste->ste_extra_flags = prev->ste_extra_flags;
}
/* No need to inherit ste_mangled_names in classes, where all names
* are mangled. */
Expand Down Expand Up @@ -2291,12 +2294,18 @@ 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, int is_default)
{
if (e) {
int is_in_class = st->st_cur->ste_can_see_class_scope;
if (!symtable_enter_block(st, name, TypeVarBoundBlock, key, LOCATION(e)))
return 0;

st->st_cur->ste_extra_flags &= CLEAR_TYPE_PARAM_ATTR_EXTRA_FLAGS;
st->st_cur->ste_extra_flags |= (is_default == 1 ? InTypeParamDefault : (
e->kind == Tuple_kind ? InTypeParamConstraint : InTypeParamBound
));

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);
Expand All @@ -2321,36 +2330,47 @@ 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);

st->st_cur->ste_extra_flags &= CLEAR_TYPE_PARAM_KIND_EXTRA_FLAGS;
st->st_cur->ste_extra_flags |= InTypeVar;

// 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
// the comprehension scope. Therefore, use the address + 1 as the second key.
// 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, 0)) {
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), 1)) {
VISIT_QUIT(st, 0);
}
break;
case TypeVarTuple_kind:
if (!symtable_add_def(st, tp->v.TypeVarTuple.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp))) {
VISIT_QUIT(st, 0);
}

st->st_cur->ste_extra_flags &= CLEAR_TYPE_PARAM_KIND_EXTRA_FLAGS;
st->st_cur->ste_extra_flags |= InTypeVarTuple;

if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVarTuple.default_value, tp->v.TypeVarTuple.name,
(void *)tp)) {
(void *)tp, 1)) {
VISIT_QUIT(st, 0);
}
break;
case ParamSpec_kind:
if (!symtable_add_def(st, tp->v.ParamSpec.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp))) {
VISIT_QUIT(st, 0);
}

st->st_cur->ste_extra_flags &= CLEAR_TYPE_PARAM_KIND_EXTRA_FLAGS;
st->st_cur->ste_extra_flags |= InParamSpec;

if (!symtable_visit_type_param_bound_or_default(st, tp->v.ParamSpec.default_value, tp->v.ParamSpec.name,
(void *)tp)) {
(void *)tp, 1)) {
VISIT_QUIT(st, 0);
}
break;
Expand Down Expand Up @@ -2732,8 +2752,22 @@ 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 == TypeVarBoundBlock) {
_extra_flags_t flags = st->st_cur->ste_extra_flags;
PyErr_Format(
PyExc_SyntaxError, EXPR_NOT_ALLOWED_IN_TYPE_PARAM_BLOCK, name, (
(flags & InTypeVar) ? "a TypeVar" :
(flags & InTypeVarTuple) ? "a TypeVarTuple" :
(flags & InParamSpec) ? "a ParamSpec" :
"a TypeVar, TypeVarTuple or a ParamSpec"
), (
(flags & InTypeParamBound) ? " bound" :
(flags & InTypeParamConstraint) ? " constraint" :
(flags & InTypeParamDefault) ? " default" :
""
)
);
}
else if (type == TypeAliasBlock)
PyErr_Format(PyExc_SyntaxError, TYPEALIAS_NOT_ALLOWED, name);
else if (type == TypeParamBlock)
Expand Down