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

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Bénédikt Tran 2024-06-17 15:51:03 +02:00 committed by GitHub
parent 274f844830
commit 4bf17c381f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 277 additions and 55 deletions

View file

@ -58,13 +58,13 @@
#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;
@ -269,9 +271,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) {
@ -544,9 +546,9 @@ _PyST_IsFunctionLike(PySTEntryObject *ste)
{
return ste->ste_type == FunctionBlock
|| ste->ste_type == AnnotationBlock
|| ste->ste_type == TypeVarBoundBlock
|| ste->ste_type == TypeVariableBlock
|| ste->ste_type == TypeAliasBlock
|| ste->ste_type == TypeParamBlock;
|| ste->ste_type == TypeParametersBlock;
}
static int
@ -1519,7 +1521,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;
}
@ -2122,20 +2124,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:
@ -2341,19 +2343,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;
}
@ -2371,6 +2381,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
@ -2378,11 +2394,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;
@ -2390,8 +2407,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;
@ -2399,8 +2417,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;
@ -2829,12 +2848,21 @@ symtable_raise_if_annotation_block(struct symtable *st, const char *name, expr_t
_Py_block_ty 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;