Skip to content

Commit 07539c8

Browse files
committed
gh-159672: Retain tokens for syntax error diagnostics
1 parent b00a140 commit 07539c8

3 files changed

Lines changed: 48 additions & 20 deletions

File tree

‎Parser/pegen.c‎

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,28 @@ _resize_tokens_array(Parser *p) {
327327
return 0;
328328
}
329329

330+
int
331+
_PyPegen_append_token(Parser *p, struct token *token)
332+
{
333+
int type = token->type;
334+
// Single input inserts a final newline and may still need dedents. Apply
335+
// the same normalization when the error pass fills the replay cache.
336+
if (p->start_rule == Py_single_input && type == ENDMARKER && p->parsing_started) {
337+
type = NEWLINE;
338+
p->parsing_started = 0;
339+
if (!(p->flags & PyPARSE_DONT_IMPLY_DEDENT)) {
340+
_PyTokenizer_ImplyDedents(p->tok);
341+
}
342+
}
343+
else {
344+
p->parsing_started = 1;
345+
}
346+
if (p->fill == p->size && _resize_tokens_array(p) < 0) {
347+
return -1;
348+
}
349+
return initialize_token(p, p->tokens[p->fill], token, type);
350+
}
351+
330352
int
331353
_PyPegen_fill_token(Parser *p)
332354
{
@@ -363,26 +385,7 @@ _PyPegen_fill_token(Parser *p)
363385
p->input_exhausted = 1;
364386
}
365387

366-
// If we have reached the end and we are in single input mode we need to insert a newline and reset the parsing
367-
if (p->start_rule == Py_single_input && type == ENDMARKER && p->parsing_started) {
368-
type = NEWLINE; /* Add an extra newline */
369-
p->parsing_started = 0;
370-
371-
if (!(p->flags & PyPARSE_DONT_IMPLY_DEDENT)) {
372-
_PyTokenizer_ImplyDedents(p->tok);
373-
}
374-
}
375-
else {
376-
p->parsing_started = 1;
377-
}
378-
379-
// Check if we are at the limit of the token array capacity and resize if needed
380-
if ((p->fill == p->size) && (_resize_tokens_array(p) != 0)) {
381-
goto error;
382-
}
383-
384-
Token *t = p->tokens[p->fill];
385-
int result = initialize_token(p, t, &new_token, type);
388+
int result = _PyPegen_append_token(p, &new_token);
386389
_PyToken_Free(&new_token);
387390
return result;
388391
error:

‎Parser/pegen.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929

3030
#define CURRENT_POS (-5)
3131

32+
// Error-only token replay is bounded independently of the source length.
33+
#define MAX_KEYWORD_TYPO_TOKENS 4096
34+
3235
typedef struct _memo {
3336
int type;
3437
void *node;
@@ -193,6 +196,7 @@ expr_ty _PyPegen_soft_keyword_token(Parser *p);
193196
expr_ty _PyPegen_fstring_middle_token(Parser* p);
194197
Token *_PyPegen_get_last_nonnwhitespace_token(Parser *);
195198
int _PyPegen_fill_token(Parser *p);
199+
int _PyPegen_append_token(Parser *p, struct token *token);
196200
// Return -1 on failure, leaving the buffer and capacity unchanged.
197201
int _PyPegen_grow_loop_buffer(void ***buffer, Py_ssize_t *capacity);
198202
expr_ty _PyPegen_name_token(Parser *p);

‎Parser/pegen_errors.c‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,24 @@ _PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) {
154154
Py_ssize_t current_err_line = current_token->lineno;
155155

156156
int ret = 0;
157+
int cache_tokens = 1;
157158
struct token new_token;
158159
_PyToken_Init(&new_token);
159160

160161
for (;;) {
161162
_PyTokenizer_Get(p->tok, &new_token);
163+
// Retain the tokens this error check already reads for bounded typo
164+
// trials. Never treat a truncated cache as complete input.
165+
if (cache_tokens && p->fill < MAX_KEYWORD_TYPO_TOKENS &&
166+
(p->fill == 0 || p->tokens[p->fill - 1]->type != ENDMARKER) &&
167+
new_token.type != ERRORTOKEN && new_token.type != TYPE_IGNORE &&
168+
_PyPegen_append_token(p, &new_token) < 0)
169+
{
170+
// Caching is optional; its allocation failures must not replace
171+
// the syntax error we are already reporting.
172+
PyErr_Clear();
173+
cache_tokens = 0;
174+
}
162175
switch (new_token.type) {
163176
case ERRORTOKEN: {
164177
if (PyErr_Occurred()) {
@@ -177,6 +190,14 @@ _PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) {
177190
break;
178191
}
179192
case ENDMARKER:
193+
if (cache_tokens && p->fill < MAX_KEYWORD_TYPO_TOKENS &&
194+
p->start_rule == Py_single_input && p->fill > 0 &&
195+
p->tokens[p->fill - 1]->type == NEWLINE)
196+
{
197+
// append_token inserted single input's implicit newline;
198+
// retain any pending dedents and the actual end marker.
199+
continue;
200+
}
180201
break;
181202
default:
182203
continue;

0 commit comments

Comments
 (0)