Skip to content
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

bpo-36878: Track extra text added to 'type: ignore' in the AST #13479

Merged
merged 1 commit into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions Include/Python-ast.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Lib/test/test_type_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,16 @@ def test_vardecl(self):

def test_ignores(self):
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)
self.assertEqual(tree.type_ignores, [])

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,7 @@ Daniel Stutzbach
Andreas Stührk
Colin Su
Pal Subbiah
Michael J. Sullivan
Nathan Sullivan
Mark Summerfield
Reuben Sumner
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 1 addition & 2 deletions Parser/Python.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,5 @@ module Python

withitem = (expr context_expr, expr? optional_vars)

type_ignore = TypeIgnore(int lineno)
type_ignore = TypeIgnore(int lineno, string tag)
}

43 changes: 28 additions & 15 deletions Parser/parsetok.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ static node *parsetok(struct tok_state *, grammar *, int, perrdetail *, int *);
static int initerr(perrdetail *err_ret, PyObject * filename);

typedef struct {
int *items;
struct {
int lineno;
char *comment;
} *items;
size_t size;
size_t num_items;
} growable_int_array;
} growable_comment_array;

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);
arr->items = malloc(initial_size * sizeof(*arr->items));
arr->size = initial_size;
Expand All @@ -32,7 +35,7 @@ growable_int_array_init(growable_int_array *arr, size_t initial_size) {
}

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) {
arr->size *= 2;
arr->items = realloc(arr->items, arr->size * sizeof(*arr->items));
Expand All @@ -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++;
return 1;
}

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);
}

Expand Down Expand Up @@ -220,9 +227,9 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
node *n;
int started = 0;
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;
PyTokenizer_Free(tok);
return NULL;
Expand Down Expand Up @@ -320,8 +327,7 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
}

if (type == TYPE_IGNORE) {
PyObject_FREE(str);
if (!growable_int_array_add(&type_ignores, tok->lineno)) {
if (!growable_comment_array_add(&type_ignores, tok->lineno, str)) {
err_ret->error = E_NOMEM;
break;
}
Expand Down Expand Up @@ -355,17 +361,24 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
REQ(ch, ENDMARKER);

for (i = 0; i < type_ignores.num_items; i++) {
PyNode_AddChild(ch, TYPE_IGNORE, NULL,
type_ignores.items[i], 0,
type_ignores.items[i], 0);
int res = PyNode_AddChild(ch, TYPE_IGNORE, type_ignores.items[i].comment,
type_ignores.items[i].lineno, 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;
}
}

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

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

growable_int_array_deallocate(&type_ignores);
growable_comment_array_deallocate(&type_ignores);

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
*flags = ps->p_flags;
Expand Down
8 changes: 6 additions & 2 deletions Parser/tokenizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1269,17 +1269,21 @@ 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. */
if (!*prefix) {
int is_type_ignore = 1;
const char *ignore_end = p + 6;
tok_backup(tok, c); /* don't eat the newline or EOF */

type_start = p;

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

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 (blankline) {
tok_nextc(tok);
Expand Down
33 changes: 30 additions & 3 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Python/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,10 @@ PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags,
goto out;

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)
goto out;
asdl_seq_SET(type_ignores, i, ti);
Expand Down