Skip to content

Commit 933e150

Browse files
msullivanilevkivskyi
authored andcommitted
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.
1 parent 4c7a46e commit 933e150

File tree

9 files changed

+86
-26
lines changed

9 files changed

+86
-26
lines changed

Include/Python-ast.h

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_type_comments.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,16 @@ def test_vardecl(self):
272272

273273
def test_ignores(self):
274274
for tree in self.parse_all(ignores):
275-
self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5, 8, 9, 10, 11])
275+
self.assertEqual(
276+
[(ti.lineno, ti.tag) for ti in tree.type_ignores],
277+
[
278+
(2, ''),
279+
(5, ''),
280+
(8, '[excuse]'),
281+
(9, '=excuse'),
282+
(10, ' [excuse]'),
283+
(11, ' whatever'),
284+
])
276285
tree = self.classic_parse(ignores)
277286
self.assertEqual(tree.type_ignores, [])
278287

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,7 @@ Daniel Stutzbach
15941594
Andreas Stührk
15951595
Colin Su
15961596
Pal Subbiah
1597+
Michael J. Sullivan
15971598
Nathan Sullivan
15981599
Mark Summerfield
15991600
Reuben Sumner
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Store text appearing after a `# type: ignore` comment in the AST. For
2+
example a type ignore like `# type: ignore[E1000]` will have the string
3+
`"[E1000]"` stored in its AST node.

Parser/Python.asdl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,5 @@ module Python
125125

126126
withitem = (expr context_expr, expr? optional_vars)
127127

128-
type_ignore = TypeIgnore(int lineno)
128+
type_ignore = TypeIgnore(int lineno, string tag)
129129
}
130-

Parser/parsetok.c

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ static node *parsetok(struct tok_state *, grammar *, int, perrdetail *, int *);
1616
static int initerr(perrdetail *err_ret, PyObject * filename);
1717

1818
typedef struct {
19-
int *items;
19+
struct {
20+
int lineno;
21+
char *comment;
22+
} *items;
2023
size_t size;
2124
size_t num_items;
22-
} growable_int_array;
25+
} growable_comment_array;
2326

2427
static int
25-
growable_int_array_init(growable_int_array *arr, size_t initial_size) {
28+
growable_comment_array_init(growable_comment_array *arr, size_t initial_size) {
2629
assert(initial_size > 0);
2730
arr->items = malloc(initial_size * sizeof(*arr->items));
2831
arr->size = initial_size;
@@ -32,7 +35,7 @@ growable_int_array_init(growable_int_array *arr, size_t initial_size) {
3235
}
3336

3437
static int
35-
growable_int_array_add(growable_int_array *arr, int item) {
38+
growable_comment_array_add(growable_comment_array *arr, int lineno, char *comment) {
3639
if (arr->num_items >= arr->size) {
3740
arr->size *= 2;
3841
arr->items = realloc(arr->items, arr->size * sizeof(*arr->items));
@@ -41,13 +44,17 @@ growable_int_array_add(growable_int_array *arr, int item) {
4144
}
4245
}
4346

44-
arr->items[arr->num_items] = item;
47+
arr->items[arr->num_items].lineno = lineno;
48+
arr->items[arr->num_items].comment = comment;
4549
arr->num_items++;
4650
return 1;
4751
}
4852

4953
static void
50-
growable_int_array_deallocate(growable_int_array *arr) {
54+
growable_comment_array_deallocate(growable_comment_array *arr) {
55+
for (unsigned i = 0; i < arr->num_items; i++) {
56+
PyObject_FREE(arr->items[i].comment);
57+
}
5158
free(arr->items);
5259
}
5360

@@ -220,9 +227,9 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
220227
node *n;
221228
int started = 0;
222229
int col_offset, end_col_offset;
223-
growable_int_array type_ignores;
230+
growable_comment_array type_ignores;
224231

225-
if (!growable_int_array_init(&type_ignores, 10)) {
232+
if (!growable_comment_array_init(&type_ignores, 10)) {
226233
err_ret->error = E_NOMEM;
227234
PyTokenizer_Free(tok);
228235
return NULL;
@@ -320,8 +327,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
320327
}
321328

322329
if (type == TYPE_IGNORE) {
323-
PyObject_FREE(str);
324-
if (!growable_int_array_add(&type_ignores, tok->lineno)) {
330+
if (!growable_comment_array_add(&type_ignores, tok->lineno, str)) {
325331
err_ret->error = E_NOMEM;
326332
break;
327333
}
@@ -355,17 +361,24 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
355361
REQ(ch, ENDMARKER);
356362

357363
for (i = 0; i < type_ignores.num_items; i++) {
358-
PyNode_AddChild(ch, TYPE_IGNORE, NULL,
359-
type_ignores.items[i], 0,
360-
type_ignores.items[i], 0);
364+
int res = PyNode_AddChild(ch, TYPE_IGNORE, type_ignores.items[i].comment,
365+
type_ignores.items[i].lineno, 0,
366+
type_ignores.items[i].lineno, 0);
367+
if (res != 0) {
368+
err_ret->error = res;
369+
PyNode_Free(n);
370+
n = NULL;
371+
break;
372+
}
373+
type_ignores.items[i].comment = NULL;
361374
}
362375
}
363376

364377
/* Check that the source for a single input statement really
365378
is a single statement by looking at what is left in the
366379
buffer after parsing. Trailing whitespace and comments
367380
are OK. */
368-
if (start == single_input) {
381+
if (err_ret->error == E_DONE && start == single_input) {
369382
char *cur = tok->cur;
370383
char c = *tok->cur;
371384

@@ -392,7 +405,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
392405
else
393406
n = NULL;
394407

395-
growable_int_array_deallocate(&type_ignores);
408+
growable_comment_array_deallocate(&type_ignores);
396409

397410
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
398411
*flags = ps->p_flags;

Parser/tokenizer.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,17 +1269,21 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
12691269
/* This is a type comment if we matched all of type_comment_prefix. */
12701270
if (!*prefix) {
12711271
int is_type_ignore = 1;
1272+
const char *ignore_end = p + 6;
12721273
tok_backup(tok, c); /* don't eat the newline or EOF */
12731274

12741275
type_start = p;
12751276

12761277
/* A TYPE_IGNORE is "type: ignore" followed by the end of the token
12771278
* or anything non-alphanumeric. */
12781279
is_type_ignore = (
1279-
tok->cur >= p + 6 && memcmp(p, "ignore", 6) == 0
1280-
&& !(tok->cur > p + 6 && isalnum(p[6])));
1280+
tok->cur >= ignore_end && memcmp(p, "ignore", 6) == 0
1281+
&& !(tok->cur > ignore_end && isalnum(p[6])));
12811282

12821283
if (is_type_ignore) {
1284+
*p_start = (char *) ignore_end;
1285+
*p_end = tok->cur;
1286+
12831287
/* If this type ignore is the only thing on the line, consume the newline also. */
12841288
if (blankline) {
12851289
tok_nextc(tok);

Python/Python-ast.c

Lines changed: 30 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/ast.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,10 @@ PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags,
830830
goto out;
831831

832832
for (i = 0; i < num; i++) {
833-
type_ignore_ty ti = TypeIgnore(LINENO(CHILD(ch, i)), arena);
833+
string type_comment = new_type_comment(STR(CHILD(ch, i)), &c);
834+
if (!type_comment)
835+
goto out;
836+
type_ignore_ty ti = TypeIgnore(LINENO(CHILD(ch, i)), type_comment, arena);
834837
if (!ti)
835838
goto out;
836839
asdl_seq_SET(type_ignores, i, ti);

0 commit comments

Comments
 (0)