Coverage Report

Created: 2026-01-09 06:26

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