Skip to content

Commit 1d56ef5

Browse files
committed
gh-153569: include token types and owned metadata in tokenizer results
1 parent 97b6803 commit 1d56ef5

9 files changed

Lines changed: 47 additions & 28 deletions

File tree

Lib/test/test_fstring.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,9 @@ def __format__(self, format):
18911891
self.assertEqual(f"{UnchangedFormat():{r'\xFF'}}", '\\xFF')
18921892
self.assertEqual(rf"{UnchangedFormat():{r'\xFF'}}", '\\xFF')
18931893

1894+
self.assertEqual(rf"{UnchangedFormat():{f'\xFF'}}\n", 'ÿ\\n')
1895+
self.assertEqual(f"{UnchangedFormat():{rf'\xFF'}}\n", '\\xFF\n')
1896+
18941897
# Test continuation character in format specs
18951898
self.assertEqual(f"""{UnchangedFormat():{'a'\
18961899
'b'}}""", 'ab')

Lib/test/test_tstring.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ def test_raw_tstrings(self):
159159
t = tr"{path}\Documents"
160160
self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")])
161161

162+
value = 42
163+
t = rt"{value:{f'\xFF'}}\n"
164+
self.assertTStringEqual(
165+
t, ("", "\\n"), [(value, "value", None, 'ÿ')])
166+
t = t"{value:{rf'\xFF'}}\n"
167+
self.assertTStringEqual(
168+
t, ("", "\n"), [(value, "value", None, '\\xFF')])
169+
162170
def test_template_concatenation(self):
163171
# Test template + template
164172
t1 = t"Hello, "

Parser/lexer/lexer.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,9 +668,10 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token
668668
}
669669

670670

671-
int
671+
void
672672
_PyTokenizer_Get(struct tok_state *tok, struct token *token)
673673
{
674+
_PyToken_Free(token);
674675
ftstring_state *current = _PyLexer_CurrentFTString(tok);
675676
int result;
676677
if (current == NULL) {
@@ -692,5 +693,5 @@ _PyTokenizer_Get(struct tok_state *tok, struct token *token)
692693
if (tok_failed(tok)) {
693694
result = ERRORTOKEN;
694695
}
695-
return result;
696+
token->type = result;
696697
}

Parser/lexer/state.c

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,14 @@ _PyTokenizer_Free(struct tok_state *tok)
124124
PyMem_Free(tok);
125125
}
126126

127-
void
128-
_PyToken_Free(struct token *token) {
129-
Py_XDECREF(token->metadata);
130-
}
131-
132127
void
133128
_PyToken_Init(struct token *token) {
134-
#ifdef Py_DEBUG
135-
token->span = (_PyTok_Span){-1, -1};
136-
token->start_loc = (_PyTok_Loc){-1, -1};
137-
token->end_loc = (_PyTok_Loc){-1, -1};
138-
#endif
139-
token->metadata = NULL;
129+
*token = (struct token){
130+
.type = -1,
131+
.span = {-1, -1},
132+
.start_loc = {-1, -1},
133+
.end_loc = {-1, -1},
134+
};
140135
}
141136

142137
int

Parser/pegen.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ initialize_token(Parser *p, Token *parser_token, struct token *new_token, int to
206206
parser_token->metadata = NULL;
207207
if (new_token->metadata != NULL) {
208208
if (_PyArena_AddPyObject(p->arena, new_token->metadata) < 0) {
209-
Py_DECREF(new_token->metadata);
210209
return -1;
211210
}
212211
parser_token->metadata = new_token->metadata;
@@ -260,10 +259,10 @@ _PyPegen_fill_token(Parser *p)
260259
{
261260
struct token new_token;
262261
_PyToken_Init(&new_token);
263-
int type = _PyTokenizer_Get(p->tok, &new_token);
262+
_PyTokenizer_Get(p->tok, &new_token);
264263

265264
// Record and skip '# type: ignore' comments
266-
while (type == TYPE_IGNORE) {
265+
while (new_token.type == TYPE_IGNORE) {
267266
Py_ssize_t len;
268267
const char *text = _PyToken_TextView(p->tok, &new_token, &len);
269268
char *tag = PyMem_Malloc((size_t)len + 1);
@@ -278,9 +277,11 @@ _PyPegen_fill_token(Parser *p)
278277
PyErr_NoMemory();
279278
goto error;
280279
}
281-
type = _PyTokenizer_Get(p->tok, &new_token);
280+
_PyTokenizer_Get(p->tok, &new_token);
282281
}
283282

283+
int type = new_token.type;
284+
284285
// If we have reached the end and we are in single input mode we need to insert a newline and reset the parsing
285286
if (p->start_rule == Py_single_input && type == ENDMARKER && p->parsing_started) {
286287
type = NEWLINE; /* Add an extra newline */
@@ -300,7 +301,9 @@ _PyPegen_fill_token(Parser *p)
300301
}
301302

302303
Token *t = p->tokens[p->fill];
303-
return initialize_token(p, t, &new_token, type);
304+
int result = initialize_token(p, t, &new_token, type);
305+
_PyToken_Free(&new_token);
306+
return result;
304307
error:
305308
_PyToken_Free(&new_token);
306309
return -1;

Parser/pegen_errors.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ _PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) {
138138
_PyToken_Init(&new_token);
139139

140140
for (;;) {
141-
switch (_PyTokenizer_Get(p->tok, &new_token)) {
141+
_PyTokenizer_Get(p->tok, &new_token);
142+
switch (new_token.type) {
142143
case ERRORTOKEN: {
143144
if (PyErr_Occurred()) {
144145
ret = -1;

Parser/tokenizer/api.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ _PyTokenizer_SpanView(const struct tok_state *tok, _PyTok_Span span,
5757

5858
void
5959
_PyToken_GetView(const struct tok_state *tok, const struct token *token,
60-
int type, _PyToken_View *view)
60+
_PyToken_View *view)
6161
{
6262
assert(view != NULL);
6363
assert((token->span.start == -1 && token->span.end == -1) ||
@@ -69,7 +69,7 @@ _PyToken_GetView(const struct tok_state *tok, const struct token *token,
6969
? NULL : _PyLexer_BufferPointer(tok, token->span.start);
7070
view->length = token->span.end - token->span.start;
7171
view->end_line = _PyLexer_BufferPointer(tok, tok->line_start);
72-
view->line = ISSTRINGLIT(type)
72+
view->line = ISSTRINGLIT(token->type)
7373
? view->text - token->start_loc.byte_col : view->end_line;
7474
view->line_length = tok->inp - tok->line_start +
7575
(view->end_line - view->line);

Parser/tokenizer/tokenizer.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct tok_state;
99
/* Initialize before use. metadata owns a reference released by _PyToken_Free;
1010
a consumer taking that reference must set metadata to NULL. */
1111
struct token {
12+
int type;
1213
int level;
1314
int is_raw;
1415
_PyTok_Span span;
@@ -44,22 +45,28 @@ typedef struct {
4445
const char *encoding;
4546
} _PyTokenizer_Info;
4647

47-
int _PyTokenizer_Get(struct tok_state *, struct token *);
48+
/* Get replaces the initialized token, releasing its previous metadata.
49+
Errors are returned as ERRORTOKEN, with or without a Python exception. */
50+
void _PyTokenizer_Get(struct tok_state *, struct token *);
4851
void _PyTokenizer_Free(struct tok_state *);
4952
void _PyTokenizer_raise_init_error(PyObject *filename);
5053
void _PyToken_Init(struct token *);
51-
void _PyToken_Free(struct token *);
54+
static inline void
55+
_PyToken_Free(struct token *token)
56+
{
57+
Py_CLEAR(token->metadata);
58+
}
5259

5360
/* Views and borrowed snapshot references remain valid until the tokenizer is
5461
mutated or freed. Source spans may be discarded when reading more input. */
5562
_PyTokenizer_Info _PyTokenizer_GetInfo(const struct tok_state *);
5663
/* An absent token span has a nonnull empty text view. */
5764
const char *_PyToken_TextView(
5865
const struct tok_state *, const struct token *, Py_ssize_t *);
59-
/* Pair the token with the type returned by the most recent Get. text is NULL
60-
for an absent span; line includes the token's complete physical line range. */
66+
/* Use the token from the most recent Get. text is NULL for an absent span;
67+
line includes the token's complete physical line range. */
6168
void _PyToken_GetView(
62-
const struct tok_state *tok, const struct token *token, int type,
69+
const struct tok_state *tok, const struct token *token,
6370
_PyToken_View *view);
6471
const char *_PyTokenizer_SpanView(
6572
const struct tok_state *, _PyTok_Span, Py_ssize_t *);

Python/Python-tokenize.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ tokenizeriter_next(PyObject *op)
257257
struct token token;
258258
_PyToken_Init(&token);
259259

260-
int type = _PyTokenizer_Get(it->tok, &token);
260+
_PyTokenizer_Get(it->tok, &token);
261+
int type = token.type;
261262
if (type == ERRORTOKEN) {
262263
if(!PyErr_Occurred()) {
263264
_tokenizer_error(it);
@@ -271,7 +272,7 @@ tokenizeriter_next(PyObject *op)
271272
goto exit;
272273
}
273274
_PyToken_View view;
274-
_PyToken_GetView(it->tok, &token, type, &view);
275+
_PyToken_GetView(it->tok, &token, &view);
275276
const char *token_start = view.text;
276277
PyObject *str;
277278
if (token.span.start < 0) {

0 commit comments

Comments
 (0)