Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Objects/templateobject.c
Line
Count
Source (jump to first uncovered line)
1
/* t-string Template object implementation */
2
3
#include "Python.h"
4
#include "pycore_interpolation.h" // _PyInterpolation_CheckExact()
5
#include "pycore_runtime.h"       // _Py_STR()
6
#include "pycore_template.h"
7
8
typedef struct {
9
    PyObject_HEAD
10
    PyObject *stringsiter;
11
    PyObject *interpolationsiter;
12
    int from_strings;
13
} templateiterobject;
14
15
#define templateiterobject_CAST(op) \
16
0
    (assert(_PyTemplateIter_CheckExact(op)), _Py_CAST(templateiterobject*, (op)))
17
18
static PyObject *
19
templateiter_next(PyObject *op)
20
0
{
21
0
    templateiterobject *self = templateiterobject_CAST(op);
22
0
    PyObject *item;
23
0
    if (self->from_strings) {
24
0
        item = PyIter_Next(self->stringsiter);
25
0
        self->from_strings = 0;
26
0
        if (item == NULL) {
27
0
            return NULL;
28
0
        }
29
0
        if (PyUnicode_GET_LENGTH(item) == 0) {
30
0
            Py_SETREF(item, PyIter_Next(self->interpolationsiter));
31
0
            self->from_strings = 1;
32
0
        }
33
0
    } else {
34
0
        item = PyIter_Next(self->interpolationsiter);
35
0
        self->from_strings = 1;
36
0
    }
37
0
    return item;
38
0
}
39
40
static void
41
templateiter_dealloc(PyObject *op)
42
0
{
43
0
    PyObject_GC_UnTrack(op);
44
0
    Py_TYPE(op)->tp_clear(op);
45
0
    Py_TYPE(op)->tp_free(op);
46
0
}
47
48
static int
49
templateiter_clear(PyObject *op)
50
0
{
51
0
    templateiterobject *self = templateiterobject_CAST(op);
52
0
    Py_CLEAR(self->stringsiter);
53
0
    Py_CLEAR(self->interpolationsiter);
54
0
    return 0;
55
0
}
56
57
static int
58
templateiter_traverse(PyObject *op, visitproc visit, void *arg)
59
0
{
60
0
    templateiterobject *self = templateiterobject_CAST(op);
61
0
    Py_VISIT(self->stringsiter);
62
0
    Py_VISIT(self->interpolationsiter);
63
0
    return 0;
64
0
}
65
66
PyTypeObject _PyTemplateIter_Type = {
67
    PyVarObject_HEAD_INIT(NULL, 0)
68
    .tp_name = "string.templatelib.TemplateIter",
69
    .tp_doc = PyDoc_STR("Template iterator object"),
70
    .tp_basicsize = sizeof(templateiterobject),
71
    .tp_itemsize = 0,
72
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
73
    .tp_alloc = PyType_GenericAlloc,
74
    .tp_dealloc = templateiter_dealloc,
75
    .tp_clear = templateiter_clear,
76
    .tp_free = PyObject_GC_Del,
77
    .tp_traverse = templateiter_traverse,
78
    .tp_iter = PyObject_SelfIter,
79
    .tp_iternext = templateiter_next,
80
};
81
82
typedef struct {
83
    PyObject_HEAD
84
    PyObject *strings;
85
    PyObject *interpolations;
86
} templateobject;
87
88
#define templateobject_CAST(op) \
89
0
    (assert(_PyTemplate_CheckExact(op)), _Py_CAST(templateobject*, (op)))
90
91
static PyObject *
92
template_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
93
0
{
94
0
    if (kwds != NULL) {
95
0
        PyErr_SetString(PyExc_TypeError, "Template.__new__ only accepts *args arguments");
96
0
        return NULL;
97
0
    }
98
99
0
    Py_ssize_t argslen = PyTuple_GET_SIZE(args);
100
0
    Py_ssize_t stringslen = 0;
101
0
    Py_ssize_t interpolationslen = 0;
102
0
    int last_was_str = 0;
103
104
0
    for (Py_ssize_t i = 0; i < argslen; i++) {
105
0
        PyObject *item = PyTuple_GET_ITEM(args, i);
106
0
        if (PyUnicode_Check(item)) {
107
0
            if (!last_was_str) {
108
0
                stringslen++;
109
0
            }
110
0
            last_was_str = 1;
111
0
        }
112
0
        else if (_PyInterpolation_CheckExact(item)) {
113
0
            if (!last_was_str) {
114
0
                stringslen++;
115
0
            }
116
0
            interpolationslen++;
117
0
            last_was_str = 0;
118
0
        }
119
0
        else {
120
0
            PyErr_Format(
121
0
                PyExc_TypeError,
122
0
                "Template.__new__ *args need to be of type 'str' or 'Interpolation', got %T",
123
0
                item);
124
0
            return NULL;
125
0
        }
126
0
    }
127
0
    if (!last_was_str) {
128
0
        stringslen++;
129
0
    }
130
131
0
    PyObject *strings = PyTuple_New(stringslen);
132
0
    if (!strings) {
133
0
        return NULL;
134
0
    }
135
136
0
    PyObject *interpolations = PyTuple_New(interpolationslen);
137
0
    if (!interpolations) {
138
0
        Py_DECREF(strings);
139
0
        return NULL;
140
0
    }
141
142
0
    last_was_str = 0;
143
0
    Py_ssize_t stringsidx = 0, interpolationsidx = 0;
144
0
    for (Py_ssize_t i = 0; i < argslen; i++) {
145
0
        PyObject *item = PyTuple_GET_ITEM(args, i);
146
0
        if (PyUnicode_Check(item)) {
147
0
            if (last_was_str) {
148
0
                PyObject *laststring = PyTuple_GET_ITEM(strings, stringsidx - 1);
149
0
                PyObject *concat = PyUnicode_Concat(laststring, item);
150
0
                Py_DECREF(laststring);
151
0
                if (!concat) {
152
0
                    Py_DECREF(strings);
153
0
                    Py_DECREF(interpolations);
154
0
                    return NULL;
155
0
                }
156
0
                PyTuple_SET_ITEM(strings, stringsidx - 1, concat);
157
0
            }
158
0
            else {
159
0
                PyTuple_SET_ITEM(strings, stringsidx++, Py_NewRef(item));
160
0
            }
161
0
            last_was_str = 1;
162
0
        }
163
0
        else if (_PyInterpolation_CheckExact(item)) {
164
0
            if (!last_was_str) {
165
0
                _Py_DECLARE_STR(empty, "");
166
0
                PyTuple_SET_ITEM(strings, stringsidx++, &_Py_STR(empty));
167
0
            }
168
0
            PyTuple_SET_ITEM(interpolations, interpolationsidx++, Py_NewRef(item));
169
0
            last_was_str = 0;
170
0
        }
171
0
    }
172
0
    if (!last_was_str) {
173
0
        _Py_DECLARE_STR(empty, "");
174
0
        PyTuple_SET_ITEM(strings, stringsidx++, &_Py_STR(empty));
175
0
    }
176
177
0
    PyObject *template = _PyTemplate_Build(strings, interpolations);
178
0
    Py_DECREF(strings);
179
0
    Py_DECREF(interpolations);
180
0
    return template;
181
0
}
182
183
static void
184
template_dealloc(PyObject *op)
185
0
{
186
0
    PyObject_GC_UnTrack(op);
187
0
    Py_TYPE(op)->tp_clear(op);
188
0
    Py_TYPE(op)->tp_free(op);
189
0
}
190
191
static int
192
template_clear(PyObject *op)
193
0
{
194
0
    templateobject *self = templateobject_CAST(op);
195
0
    Py_CLEAR(self->strings);
196
0
    Py_CLEAR(self->interpolations);
197
0
    return 0;
198
0
}
199
200
static int
201
template_traverse(PyObject *op, visitproc visit, void *arg)
202
0
{
203
0
    templateobject *self = templateobject_CAST(op);
204
0
    Py_VISIT(self->strings);
205
0
    Py_VISIT(self->interpolations);
206
0
    return 0;
207
0
}
208
209
static PyObject *
210
template_repr(PyObject *op)
211
0
{
212
0
    templateobject *self = templateobject_CAST(op);
213
0
    return PyUnicode_FromFormat("%s(strings=%R, interpolations=%R)",
214
0
                                _PyType_Name(Py_TYPE(self)),
215
0
                                self->strings,
216
0
                                self->interpolations);
217
0
}
218
219
static PyObject *
220
template_iter(PyObject *op)
221
0
{
222
0
    templateobject *self = templateobject_CAST(op);
223
0
    templateiterobject *iter = PyObject_GC_New(templateiterobject, &_PyTemplateIter_Type);
224
0
    if (iter == NULL) {
225
0
        return NULL;
226
0
    }
227
228
0
    PyObject *stringsiter = PyObject_GetIter(self->strings);
229
0
    if (stringsiter == NULL) {
230
0
        Py_DECREF(iter);
231
0
        return NULL;
232
0
    }
233
234
0
    PyObject *interpolationsiter = PyObject_GetIter(self->interpolations);
235
0
    if (interpolationsiter == NULL) {
236
0
        Py_DECREF(iter);
237
0
        Py_DECREF(stringsiter);
238
0
        return NULL;
239
0
    }
240
241
0
    iter->stringsiter = stringsiter;
242
0
    iter->interpolationsiter = interpolationsiter;
243
0
    iter->from_strings = 1;
244
0
    PyObject_GC_Track(iter);
245
0
    return (PyObject *)iter;
246
0
}
247
248
static PyObject *
249
template_strings_append_str(PyObject *strings, PyObject *str)
250
0
{
251
0
    Py_ssize_t stringslen = PyTuple_GET_SIZE(strings);
252
0
    PyObject *string = PyTuple_GET_ITEM(strings, stringslen - 1);
253
0
    PyObject *concat = PyUnicode_Concat(string, str);
254
0
    if (concat == NULL) {
255
0
        return NULL;
256
0
    }
257
258
0
    PyObject *newstrings = PyTuple_New(stringslen);
259
0
    if (newstrings == NULL) {
260
0
        Py_DECREF(concat);
261
0
        return NULL;
262
0
    }
263
264
0
    for (Py_ssize_t i = 0; i < stringslen - 1; i++) {
265
0
        PyTuple_SET_ITEM(newstrings, i, Py_NewRef(PyTuple_GET_ITEM(strings, i)));
266
0
    }
267
0
    PyTuple_SET_ITEM(newstrings, stringslen - 1, concat);
268
269
0
    return newstrings;
270
0
}
271
272
static PyObject *
273
template_strings_prepend_str(PyObject *strings, PyObject *str)
274
0
{
275
0
    Py_ssize_t stringslen = PyTuple_GET_SIZE(strings);
276
0
    PyObject *string = PyTuple_GET_ITEM(strings, 0);
277
0
    PyObject *concat = PyUnicode_Concat(str, string);
278
0
    if (concat == NULL) {
279
0
        return NULL;
280
0
    }
281
282
0
    PyObject *newstrings = PyTuple_New(stringslen);
283
0
    if (newstrings == NULL) {
284
0
        Py_DECREF(concat);
285
0
        return NULL;
286
0
    }
287
288
0
    PyTuple_SET_ITEM(newstrings, 0, concat);
289
0
    for (Py_ssize_t i = 1; i < stringslen; i++) {
290
0
        PyTuple_SET_ITEM(newstrings, i, Py_NewRef(PyTuple_GET_ITEM(strings, i)));
291
0
    }
292
293
0
    return newstrings;
294
0
}
295
296
static PyObject *
297
template_strings_concat(PyObject *left, PyObject *right)
298
0
{
299
0
    Py_ssize_t left_stringslen = PyTuple_GET_SIZE(left);
300
0
    PyObject *left_laststring = PyTuple_GET_ITEM(left, left_stringslen - 1);
301
0
    Py_ssize_t right_stringslen = PyTuple_GET_SIZE(right);
302
0
    PyObject *right_firststring = PyTuple_GET_ITEM(right, 0);
303
304
0
    PyObject *concat = PyUnicode_Concat(left_laststring, right_firststring);
305
0
    if (concat == NULL) {
306
0
        return NULL;
307
0
    }
308
309
0
    PyObject *newstrings = PyTuple_New(left_stringslen + right_stringslen - 1);
310
0
    if (newstrings == NULL) {
311
0
        Py_DECREF(concat);
312
0
        return NULL;
313
0
    }
314
315
0
    Py_ssize_t index = 0;
316
0
    for (Py_ssize_t i = 0; i < left_stringslen - 1; i++) {
317
0
        PyTuple_SET_ITEM(newstrings, index++, Py_NewRef(PyTuple_GET_ITEM(left, i)));
318
0
    }
319
0
    PyTuple_SET_ITEM(newstrings, index++, concat);
320
0
    for (Py_ssize_t i = 1; i < right_stringslen; i++) {
321
0
        PyTuple_SET_ITEM(newstrings, index++, Py_NewRef(PyTuple_GET_ITEM(right, i)));
322
0
    }
323
324
0
    return newstrings;
325
0
}
326
327
static PyObject *
328
template_concat_templates(templateobject *self, templateobject *other)
329
0
{
330
0
    PyObject *newstrings = template_strings_concat(self->strings, other->strings);
331
0
    if (newstrings == NULL) {
332
0
        return NULL;
333
0
    }
334
335
0
    PyObject *newinterpolations = PySequence_Concat(self->interpolations, other->interpolations);
336
0
    if (newinterpolations == NULL) {
337
0
        Py_DECREF(newstrings);
338
0
        return NULL;
339
0
    }
340
341
0
    PyObject *newtemplate = _PyTemplate_Build(newstrings, newinterpolations);
342
0
    Py_DECREF(newstrings);
343
0
    Py_DECREF(newinterpolations);
344
0
    return newtemplate;
345
0
}
346
347
static PyObject *
348
template_concat_template_str(templateobject *self, PyObject *other)
349
0
{
350
0
    PyObject *newstrings = template_strings_append_str(self->strings, other);
351
0
    if (newstrings == NULL) {
352
0
        return NULL;
353
0
    }
354
355
0
    PyObject *newtemplate = _PyTemplate_Build(newstrings, self->interpolations);
356
0
    Py_DECREF(newstrings);
357
0
    return newtemplate;
358
0
}
359
360
static PyObject *
361
template_concat_str_template(templateobject *self, PyObject *other)
362
0
{
363
0
    PyObject *newstrings = template_strings_prepend_str(self->strings, other);
364
0
    if (newstrings == NULL) {
365
0
        return NULL;
366
0
    }
367
368
0
    PyObject *newtemplate = _PyTemplate_Build(newstrings, self->interpolations);
369
0
    Py_DECREF(newstrings);
370
0
    return newtemplate;
371
0
}
372
373
PyObject *
374
_PyTemplate_Concat(PyObject *self, PyObject *other)
375
0
{
376
0
    if (_PyTemplate_CheckExact(self) && _PyTemplate_CheckExact(other)) {
377
0
        return template_concat_templates((templateobject *) self, (templateobject *) other);
378
0
    }
379
0
    else if ((_PyTemplate_CheckExact(self)) && PyUnicode_Check(other)) {
380
0
        return template_concat_template_str((templateobject *) self, other);
381
0
    }
382
0
    else if (PyUnicode_Check(self) && (_PyTemplate_CheckExact(other))) {
383
0
        return template_concat_str_template((templateobject *) other, self);
384
0
    }
385
0
    else {
386
0
        Py_RETURN_NOTIMPLEMENTED;
387
0
    }
388
0
}
389
390
static PyObject *
391
template_values_get(PyObject *op, void *Py_UNUSED(data))
392
0
{
393
0
    templateobject *self = templateobject_CAST(op);
394
395
0
    Py_ssize_t len = PyTuple_GET_SIZE(self->interpolations);
396
0
    PyObject *values = PyTuple_New(PyTuple_GET_SIZE(self->interpolations));
397
0
    if (values == NULL) {
398
0
        return NULL;
399
0
    }
400
401
0
    for (Py_ssize_t i = 0; i < len; i++) {
402
0
        PyObject *item = PyTuple_GET_ITEM(self->interpolations, i);
403
0
        PyTuple_SET_ITEM(values, i, _PyInterpolation_GetValueRef(item));
404
0
    }
405
406
0
    return values;
407
0
}
408
409
static PyMemberDef template_members[] = {
410
    {"strings", Py_T_OBJECT_EX, offsetof(templateobject, strings), Py_READONLY, "Strings"},
411
    {"interpolations", Py_T_OBJECT_EX, offsetof(templateobject, interpolations), Py_READONLY, "Interpolations"},
412
    {NULL},
413
};
414
415
static PyGetSetDef template_getset[] = {
416
    {"values", template_values_get, NULL,
417
     PyDoc_STR("Values of interpolations"), NULL},
418
    {NULL},
419
};
420
421
static PySequenceMethods template_as_sequence = {
422
    .sq_concat = _PyTemplate_Concat,
423
};
424
425
static PyObject*
426
template_reduce(PyObject *op, PyObject *Py_UNUSED(dummy))
427
0
{
428
0
    PyObject *mod = PyImport_ImportModule("string.templatelib");
429
0
    if (mod == NULL) {
430
0
        return NULL;
431
0
    }
432
0
    PyObject *func = PyObject_GetAttrString(mod, "_template_unpickle");
433
0
    Py_DECREF(mod);
434
0
    if (func == NULL) {
435
0
        return NULL;
436
0
    }
437
438
0
    templateobject *self = templateobject_CAST(op);
439
0
    PyObject *result = Py_BuildValue("O(OO)",
440
0
                                     func,
441
0
                                     self->strings,
442
0
                                     self->interpolations);
443
444
0
    Py_DECREF(func);
445
0
    return result;
446
0
}
447
448
static PyMethodDef template_methods[] = {
449
    {"__reduce__", template_reduce, METH_NOARGS, NULL},
450
    {"__class_getitem__", Py_GenericAlias,
451
        METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
452
    {NULL, NULL},
453
};
454
455
PyTypeObject _PyTemplate_Type = {
456
    PyVarObject_HEAD_INIT(NULL, 0)
457
    .tp_name = "string.templatelib.Template",
458
    .tp_doc = PyDoc_STR("Template object"),
459
    .tp_basicsize = sizeof(templateobject),
460
    .tp_itemsize = 0,
461
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
462
    .tp_as_sequence = &template_as_sequence,
463
    .tp_new = template_new,
464
    .tp_alloc = PyType_GenericAlloc,
465
    .tp_dealloc = template_dealloc,
466
    .tp_clear = template_clear,
467
    .tp_free = PyObject_GC_Del,
468
    .tp_repr = template_repr,
469
    .tp_members = template_members,
470
    .tp_methods = template_methods,
471
    .tp_getset = template_getset,
472
    .tp_iter = template_iter,
473
    .tp_traverse = template_traverse,
474
};
475
476
PyObject *
477
_PyTemplate_Build(PyObject *strings, PyObject *interpolations)
478
0
{
479
0
    templateobject *template = PyObject_GC_New(templateobject, &_PyTemplate_Type);
480
0
    if (template == NULL) {
481
0
        return NULL;
482
0
    }
483
484
0
    template->strings = Py_NewRef(strings);
485
0
    template->interpolations = Py_NewRef(interpolations);
486
0
    PyObject_GC_Track(template);
487
0
    return (PyObject *) template;
488
0
}