mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
bpo-36878: Track extra text added to 'type: ignore' in the AST (GH-13479)
GH-13238 made extra text after a # type: ignore accepted by the parser. This finishes the job and actually plumbs the extra text through the parser and makes it available in the AST.
This commit is contained in:
parent
4c7a46eb3c
commit
933e1509ec
9 changed files with 86 additions and 26 deletions
5
Include/Python-ast.h
generated
5
Include/Python-ast.h
generated
|
@ -467,6 +467,7 @@ struct _type_ignore {
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
int lineno;
|
int lineno;
|
||||||
|
string tag;
|
||||||
} TypeIgnore;
|
} TypeIgnore;
|
||||||
|
|
||||||
} v;
|
} v;
|
||||||
|
@ -702,8 +703,8 @@ alias_ty _Py_alias(identifier name, identifier asname, PyArena *arena);
|
||||||
#define withitem(a0, a1, a2) _Py_withitem(a0, a1, a2)
|
#define withitem(a0, a1, a2) _Py_withitem(a0, a1, a2)
|
||||||
withitem_ty _Py_withitem(expr_ty context_expr, expr_ty optional_vars, PyArena
|
withitem_ty _Py_withitem(expr_ty context_expr, expr_ty optional_vars, PyArena
|
||||||
*arena);
|
*arena);
|
||||||
#define TypeIgnore(a0, a1) _Py_TypeIgnore(a0, a1)
|
#define TypeIgnore(a0, a1, a2) _Py_TypeIgnore(a0, a1, a2)
|
||||||
type_ignore_ty _Py_TypeIgnore(int lineno, PyArena *arena);
|
type_ignore_ty _Py_TypeIgnore(int lineno, string tag, PyArena *arena);
|
||||||
|
|
||||||
PyObject* PyAST_mod2obj(mod_ty t);
|
PyObject* PyAST_mod2obj(mod_ty t);
|
||||||
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode);
|
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode);
|
||||||
|
|
|
@ -272,7 +272,16 @@ class TypeCommentTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_ignores(self):
|
def test_ignores(self):
|
||||||
for tree in self.parse_all(ignores):
|
for tree in self.parse_all(ignores):
|
||||||
self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5, 8, 9, 10, 11])
|
self.assertEqual(
|
||||||
|
[(ti.lineno, ti.tag) for ti in tree.type_ignores],
|
||||||
|
[
|
||||||
|
(2, ''),
|
||||||
|
(5, ''),
|
||||||
|
(8, '[excuse]'),
|
||||||
|
(9, '=excuse'),
|
||||||
|
(10, ' [excuse]'),
|
||||||
|
(11, ' whatever'),
|
||||||
|
])
|
||||||
tree = self.classic_parse(ignores)
|
tree = self.classic_parse(ignores)
|
||||||
self.assertEqual(tree.type_ignores, [])
|
self.assertEqual(tree.type_ignores, [])
|
||||||
|
|
||||||
|
|
|
@ -1594,6 +1594,7 @@ Daniel Stutzbach
|
||||||
Andreas Stührk
|
Andreas Stührk
|
||||||
Colin Su
|
Colin Su
|
||||||
Pal Subbiah
|
Pal Subbiah
|
||||||
|
Michael J. Sullivan
|
||||||
Nathan Sullivan
|
Nathan Sullivan
|
||||||
Mark Summerfield
|
Mark Summerfield
|
||||||
Reuben Sumner
|
Reuben Sumner
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Store text appearing after a `# type: ignore` comment in the AST. For
|
||||||
|
example a type ignore like `# type: ignore[E1000]` will have the string
|
||||||
|
`"[E1000]"` stored in its AST node.
|
|
@ -125,6 +125,5 @@ module Python
|
||||||
|
|
||||||
withitem = (expr context_expr, expr? optional_vars)
|
withitem = (expr context_expr, expr? optional_vars)
|
||||||
|
|
||||||
type_ignore = TypeIgnore(int lineno)
|
type_ignore = TypeIgnore(int lineno, string tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,16 @@ static node *parsetok(struct tok_state *, grammar *, int, perrdetail *, int *);
|
||||||
static int initerr(perrdetail *err_ret, PyObject * filename);
|
static int initerr(perrdetail *err_ret, PyObject * filename);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int *items;
|
struct {
|
||||||
|
int lineno;
|
||||||
|
char *comment;
|
||||||
|
} *items;
|
||||||
size_t size;
|
size_t size;
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
} growable_int_array;
|
} growable_comment_array;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
growable_int_array_init(growable_int_array *arr, size_t initial_size) {
|
growable_comment_array_init(growable_comment_array *arr, size_t initial_size) {
|
||||||
assert(initial_size > 0);
|
assert(initial_size > 0);
|
||||||
arr->items = malloc(initial_size * sizeof(*arr->items));
|
arr->items = malloc(initial_size * sizeof(*arr->items));
|
||||||
arr->size = initial_size;
|
arr->size = initial_size;
|
||||||
|
@ -32,7 +35,7 @@ growable_int_array_init(growable_int_array *arr, size_t initial_size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
growable_int_array_add(growable_int_array *arr, int item) {
|
growable_comment_array_add(growable_comment_array *arr, int lineno, char *comment) {
|
||||||
if (arr->num_items >= arr->size) {
|
if (arr->num_items >= arr->size) {
|
||||||
arr->size *= 2;
|
arr->size *= 2;
|
||||||
arr->items = realloc(arr->items, arr->size * sizeof(*arr->items));
|
arr->items = realloc(arr->items, arr->size * sizeof(*arr->items));
|
||||||
|
@ -41,13 +44,17 @@ growable_int_array_add(growable_int_array *arr, int item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arr->items[arr->num_items] = item;
|
arr->items[arr->num_items].lineno = lineno;
|
||||||
|
arr->items[arr->num_items].comment = comment;
|
||||||
arr->num_items++;
|
arr->num_items++;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
growable_int_array_deallocate(growable_int_array *arr) {
|
growable_comment_array_deallocate(growable_comment_array *arr) {
|
||||||
|
for (unsigned i = 0; i < arr->num_items; i++) {
|
||||||
|
PyObject_FREE(arr->items[i].comment);
|
||||||
|
}
|
||||||
free(arr->items);
|
free(arr->items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +227,9 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
|
||||||
node *n;
|
node *n;
|
||||||
int started = 0;
|
int started = 0;
|
||||||
int col_offset, end_col_offset;
|
int col_offset, end_col_offset;
|
||||||
growable_int_array type_ignores;
|
growable_comment_array type_ignores;
|
||||||
|
|
||||||
if (!growable_int_array_init(&type_ignores, 10)) {
|
if (!growable_comment_array_init(&type_ignores, 10)) {
|
||||||
err_ret->error = E_NOMEM;
|
err_ret->error = E_NOMEM;
|
||||||
PyTokenizer_Free(tok);
|
PyTokenizer_Free(tok);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -320,8 +327,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == TYPE_IGNORE) {
|
if (type == TYPE_IGNORE) {
|
||||||
PyObject_FREE(str);
|
if (!growable_comment_array_add(&type_ignores, tok->lineno, str)) {
|
||||||
if (!growable_int_array_add(&type_ignores, tok->lineno)) {
|
|
||||||
err_ret->error = E_NOMEM;
|
err_ret->error = E_NOMEM;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -355,9 +361,16 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
|
||||||
REQ(ch, ENDMARKER);
|
REQ(ch, ENDMARKER);
|
||||||
|
|
||||||
for (i = 0; i < type_ignores.num_items; i++) {
|
for (i = 0; i < type_ignores.num_items; i++) {
|
||||||
PyNode_AddChild(ch, TYPE_IGNORE, NULL,
|
int res = PyNode_AddChild(ch, TYPE_IGNORE, type_ignores.items[i].comment,
|
||||||
type_ignores.items[i], 0,
|
type_ignores.items[i].lineno, 0,
|
||||||
type_ignores.items[i], 0);
|
type_ignores.items[i].lineno, 0);
|
||||||
|
if (res != 0) {
|
||||||
|
err_ret->error = res;
|
||||||
|
PyNode_Free(n);
|
||||||
|
n = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
type_ignores.items[i].comment = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +378,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
|
||||||
is a single statement by looking at what is left in the
|
is a single statement by looking at what is left in the
|
||||||
buffer after parsing. Trailing whitespace and comments
|
buffer after parsing. Trailing whitespace and comments
|
||||||
are OK. */
|
are OK. */
|
||||||
if (start == single_input) {
|
if (err_ret->error == E_DONE && start == single_input) {
|
||||||
char *cur = tok->cur;
|
char *cur = tok->cur;
|
||||||
char c = *tok->cur;
|
char c = *tok->cur;
|
||||||
|
|
||||||
|
@ -392,7 +405,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
|
||||||
else
|
else
|
||||||
n = NULL;
|
n = NULL;
|
||||||
|
|
||||||
growable_int_array_deallocate(&type_ignores);
|
growable_comment_array_deallocate(&type_ignores);
|
||||||
|
|
||||||
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
|
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
|
||||||
*flags = ps->p_flags;
|
*flags = ps->p_flags;
|
||||||
|
|
|
@ -1269,6 +1269,7 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
|
||||||
/* This is a type comment if we matched all of type_comment_prefix. */
|
/* This is a type comment if we matched all of type_comment_prefix. */
|
||||||
if (!*prefix) {
|
if (!*prefix) {
|
||||||
int is_type_ignore = 1;
|
int is_type_ignore = 1;
|
||||||
|
const char *ignore_end = p + 6;
|
||||||
tok_backup(tok, c); /* don't eat the newline or EOF */
|
tok_backup(tok, c); /* don't eat the newline or EOF */
|
||||||
|
|
||||||
type_start = p;
|
type_start = p;
|
||||||
|
@ -1276,10 +1277,13 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
|
||||||
/* A TYPE_IGNORE is "type: ignore" followed by the end of the token
|
/* A TYPE_IGNORE is "type: ignore" followed by the end of the token
|
||||||
* or anything non-alphanumeric. */
|
* or anything non-alphanumeric. */
|
||||||
is_type_ignore = (
|
is_type_ignore = (
|
||||||
tok->cur >= p + 6 && memcmp(p, "ignore", 6) == 0
|
tok->cur >= ignore_end && memcmp(p, "ignore", 6) == 0
|
||||||
&& !(tok->cur > p + 6 && isalnum(p[6])));
|
&& !(tok->cur > ignore_end && isalnum(p[6])));
|
||||||
|
|
||||||
if (is_type_ignore) {
|
if (is_type_ignore) {
|
||||||
|
*p_start = (char *) ignore_end;
|
||||||
|
*p_end = tok->cur;
|
||||||
|
|
||||||
/* If this type ignore is the only thing on the line, consume the newline also. */
|
/* If this type ignore is the only thing on the line, consume the newline also. */
|
||||||
if (blankline) {
|
if (blankline) {
|
||||||
tok_nextc(tok);
|
tok_nextc(tok);
|
||||||
|
|
33
Python/Python-ast.c
generated
33
Python/Python-ast.c
generated
|
@ -524,8 +524,10 @@ static char *withitem_fields[]={
|
||||||
static PyTypeObject *type_ignore_type;
|
static PyTypeObject *type_ignore_type;
|
||||||
static PyObject* ast2obj_type_ignore(void*);
|
static PyObject* ast2obj_type_ignore(void*);
|
||||||
static PyTypeObject *TypeIgnore_type;
|
static PyTypeObject *TypeIgnore_type;
|
||||||
|
_Py_IDENTIFIER(tag);
|
||||||
static char *TypeIgnore_fields[]={
|
static char *TypeIgnore_fields[]={
|
||||||
"lineno",
|
"lineno",
|
||||||
|
"tag",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1164,7 +1166,7 @@ static int init_types(void)
|
||||||
if (!type_ignore_type) return 0;
|
if (!type_ignore_type) return 0;
|
||||||
if (!add_attributes(type_ignore_type, NULL, 0)) return 0;
|
if (!add_attributes(type_ignore_type, NULL, 0)) return 0;
|
||||||
TypeIgnore_type = make_type("TypeIgnore", type_ignore_type,
|
TypeIgnore_type = make_type("TypeIgnore", type_ignore_type,
|
||||||
TypeIgnore_fields, 1);
|
TypeIgnore_fields, 2);
|
||||||
if (!TypeIgnore_type) return 0;
|
if (!TypeIgnore_type) return 0;
|
||||||
initialized = 1;
|
initialized = 1;
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -2667,14 +2669,20 @@ withitem(expr_ty context_expr, expr_ty optional_vars, PyArena *arena)
|
||||||
}
|
}
|
||||||
|
|
||||||
type_ignore_ty
|
type_ignore_ty
|
||||||
TypeIgnore(int lineno, PyArena *arena)
|
TypeIgnore(int lineno, string tag, PyArena *arena)
|
||||||
{
|
{
|
||||||
type_ignore_ty p;
|
type_ignore_ty p;
|
||||||
|
if (!tag) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"field tag is required for TypeIgnore");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
p = (type_ignore_ty)PyArena_Malloc(arena, sizeof(*p));
|
p = (type_ignore_ty)PyArena_Malloc(arena, sizeof(*p));
|
||||||
if (!p)
|
if (!p)
|
||||||
return NULL;
|
return NULL;
|
||||||
p->kind = TypeIgnore_kind;
|
p->kind = TypeIgnore_kind;
|
||||||
p->v.TypeIgnore.lineno = lineno;
|
p->v.TypeIgnore.lineno = lineno;
|
||||||
|
p->v.TypeIgnore.tag = tag;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4158,6 +4166,11 @@ ast2obj_type_ignore(void* _o)
|
||||||
if (_PyObject_SetAttrId(result, &PyId_lineno, value) == -1)
|
if (_PyObject_SetAttrId(result, &PyId_lineno, value) == -1)
|
||||||
goto failed;
|
goto failed;
|
||||||
Py_DECREF(value);
|
Py_DECREF(value);
|
||||||
|
value = ast2obj_string(o->v.TypeIgnore.tag);
|
||||||
|
if (!value) goto failed;
|
||||||
|
if (_PyObject_SetAttrId(result, &PyId_tag, value) == -1)
|
||||||
|
goto failed;
|
||||||
|
Py_DECREF(value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -8738,6 +8751,7 @@ obj2ast_type_ignore(PyObject* obj, type_ignore_ty* out, PyArena* arena)
|
||||||
}
|
}
|
||||||
if (isinstance) {
|
if (isinstance) {
|
||||||
int lineno;
|
int lineno;
|
||||||
|
string tag;
|
||||||
|
|
||||||
if (_PyObject_LookupAttrId(obj, &PyId_lineno, &tmp) < 0) {
|
if (_PyObject_LookupAttrId(obj, &PyId_lineno, &tmp) < 0) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -8752,7 +8766,20 @@ obj2ast_type_ignore(PyObject* obj, type_ignore_ty* out, PyArena* arena)
|
||||||
if (res != 0) goto failed;
|
if (res != 0) goto failed;
|
||||||
Py_CLEAR(tmp);
|
Py_CLEAR(tmp);
|
||||||
}
|
}
|
||||||
*out = TypeIgnore(lineno, arena);
|
if (_PyObject_LookupAttrId(obj, &PyId_tag, &tmp) < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (tmp == NULL) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "required field \"tag\" missing from TypeIgnore");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int res;
|
||||||
|
res = obj2ast_string(tmp, &tag, arena);
|
||||||
|
if (res != 0) goto failed;
|
||||||
|
Py_CLEAR(tmp);
|
||||||
|
}
|
||||||
|
*out = TypeIgnore(lineno, tag, arena);
|
||||||
if (*out == NULL) goto failed;
|
if (*out == NULL) goto failed;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -830,7 +830,10 @@ PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
for (i = 0; i < num; i++) {
|
for (i = 0; i < num; i++) {
|
||||||
type_ignore_ty ti = TypeIgnore(LINENO(CHILD(ch, i)), arena);
|
string type_comment = new_type_comment(STR(CHILD(ch, i)), &c);
|
||||||
|
if (!type_comment)
|
||||||
|
goto out;
|
||||||
|
type_ignore_ty ti = TypeIgnore(LINENO(CHILD(ch, i)), type_comment, arena);
|
||||||
if (!ti)
|
if (!ti)
|
||||||
goto out;
|
goto out;
|
||||||
asdl_seq_SET(type_ignores, i, ti);
|
asdl_seq_SET(type_ignores, i, ti);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue