Skip to content

Commit af8d1f5

Browse files
lysnikolaoudavepecksobolevn
authored
[3.14] gh-132661: Disallow Template/str concatenation after PEP 750 spec update (#135996) (#136901)
Co-authored-by: Dave Peck <davepeck@gmail.com> Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 0d87bb6 commit af8d1f5

File tree

13 files changed

+1406
-1442
lines changed

13 files changed

+1406
-1442
lines changed

Grammar/python.gram

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,10 @@ tstring[expr_ty] (memo):
985985
_PyPegen_template_str(p, a, (asdl_expr_seq*)b, c)) }
986986

987987
string[expr_ty]: s[Token*]=STRING { _PyPegen_constant_from_string(p, s) }
988-
strings[expr_ty] (memo): a[asdl_expr_seq*]=(fstring|string|tstring)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
988+
strings[expr_ty] (memo):
989+
| invalid_string_tstring_concat
990+
| a[asdl_expr_seq*]=(fstring|string)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
991+
| a[asdl_expr_seq*]=tstring+ { _PyPegen_concatenate_tstrings(p, a, EXTRA) }
989992

990993
list[expr_ty]:
991994
| '[' a=[star_named_expressions] ']' { _PyAST_List(a, Load, EXTRA) }
@@ -1546,6 +1549,12 @@ invalid_tstring_conversion_character:
15461549
| '!' &(':' | '}') { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: missing conversion character") }
15471550
| '!' !NAME { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: invalid conversion character") }
15481551

1552+
invalid_string_tstring_concat:
1553+
| a=(fstring|string)+ b[expr_ty]=tstring {
1554+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(PyPegen_last_item(a, expr_ty), b, "cannot mix t-string literals with string or bytes literals") }
1555+
| a=tstring+ b[expr_ty]=(fstring|string) {
1556+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(PyPegen_last_item(a, expr_ty), b, "cannot mix t-string literals with string or bytes literals") }
1557+
15491558
invalid_arithmetic:
15501559
| sum ('+'|'-'|'*'|'/'|'%'|'//'|'@') a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
15511560
invalid_factor:

Lib/_ast_unparse.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -626,35 +626,11 @@ def _write_ftstring(self, values, prefix):
626626
)
627627
self._ftstring_helper(fstring_parts)
628628

629-
def _tstring_helper(self, node):
630-
if not node.values:
631-
self._write_ftstring([], "t")
632-
return
633-
last_idx = 0
634-
for i, value in enumerate(node.values):
635-
# This can happen if we have an implicit concat of a t-string
636-
# with an f-string
637-
if isinstance(value, FormattedValue):
638-
if i > last_idx:
639-
# Write t-string until here
640-
self._write_ftstring(node.values[last_idx:i], "t")
641-
self.write(" ")
642-
# Write f-string with the current formatted value
643-
self._write_ftstring([node.values[i]], "f")
644-
if i + 1 < len(node.values):
645-
# Only add a space if there are more values after this
646-
self.write(" ")
647-
last_idx = i + 1
648-
649-
if last_idx < len(node.values):
650-
# Write t-string from last_idx to end
651-
self._write_ftstring(node.values[last_idx:], "t")
652-
653629
def visit_JoinedStr(self, node):
654630
self._write_ftstring(node.values, "f")
655631

656632
def visit_TemplateStr(self, node):
657-
self._tstring_helper(node)
633+
self._write_ftstring(node.values, "t")
658634

659635
def _write_ftstring_inner(self, node, is_format_spec=False):
660636
if isinstance(node, JoinedStr):

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -999,13 +999,6 @@ def test_tstring(self):
999999
self.assertIsInstance(tree.body[0].value.values[0], ast.Constant)
10001000
self.assertIsInstance(tree.body[0].value.values[1], ast.Interpolation)
10011001

1002-
# Test AST for implicit concat of t-string with f-string
1003-
tree = ast.parse('t"Hello {name}" f"{name}"')
1004-
self.assertIsInstance(tree.body[0].value, ast.TemplateStr)
1005-
self.assertIsInstance(tree.body[0].value.values[0], ast.Constant)
1006-
self.assertIsInstance(tree.body[0].value.values[1], ast.Interpolation)
1007-
self.assertIsInstance(tree.body[0].value.values[2], ast.FormattedValue)
1008-
10091002

10101003
class CopyTests(unittest.TestCase):
10111004
"""Test copying and pickling AST nodes."""

Lib/test/test_tstring.py

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ def test_raw_tstrings(self):
150150
t = tr"{path}\Documents"
151151
self.assertTStringEqual(t, ("", r"\Documents"), [(path, "path")])
152152

153-
154153
def test_template_concatenation(self):
155154
# Test template + template
156155
t1 = t"Hello, "
@@ -161,9 +160,10 @@ def test_template_concatenation(self):
161160

162161
# Test template + string
163162
t1 = t"Hello"
164-
combined = t1 + ", world"
165-
self.assertTStringEqual(combined, ("Hello, world",), ())
166-
self.assertEqual(fstring(combined), "Hello, world")
163+
expected_msg = 'can only concatenate string.templatelib.Template ' \
164+
'\\(not "str"\\) to string.templatelib.Template'
165+
with self.assertRaisesRegex(TypeError, expected_msg):
166+
t1 + ", world"
167167

168168
# Test template + template with interpolation
169169
name = "Python"
@@ -174,9 +174,10 @@ def test_template_concatenation(self):
174174
self.assertEqual(fstring(combined), "Hello, Python")
175175

176176
# Test string + template
177-
t = "Hello, " + t"{name}"
178-
self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")])
179-
self.assertEqual(fstring(t), "Hello, Python")
177+
expected_msg = 'can only concatenate str ' \
178+
'\\(not "string.templatelib.Template"\\) to str'
179+
with self.assertRaisesRegex(TypeError, expected_msg):
180+
"Hello, " + t"{name}"
180181

181182
def test_nested_templates(self):
182183
# Test a template inside another template expression
@@ -241,52 +242,28 @@ def test_literal_concatenation(self):
241242
self.assertTStringEqual(t, ("Hello, ", ""), [(name, "name")])
242243
self.assertEqual(fstring(t), "Hello, Python")
243244

244-
# Test concatenation with string literal
245-
name = "Python"
246-
t = t"Hello, {name}" "and welcome!"
247-
self.assertTStringEqual(
248-
t, ("Hello, ", "and welcome!"), [(name, "name")]
249-
)
250-
self.assertEqual(fstring(t), "Hello, Pythonand welcome!")
251-
252-
# Test concatenation with Unicode literal
253-
name = "Python"
254-
t = t"Hello, {name}" u"and welcome!"
255-
self.assertTStringEqual(
256-
t, ("Hello, ", "and welcome!"), [(name, "name")]
257-
)
258-
self.assertEqual(fstring(t), "Hello, Pythonand welcome!")
259-
260-
# Test concatenation with f-string literal
261-
tab = '\t'
262-
t = t"Tab: {tab}. " f"f-tab: {tab}."
263-
self.assertTStringEqual(t, ("Tab: ", ". f-tab: \t."), [(tab, "tab")])
264-
self.assertEqual(fstring(t), "Tab: \t. f-tab: \t.")
265-
266-
# Test concatenation with raw string literal
267-
tab = '\t'
268-
t = t"Tab: {tab}. " r"Raw tab: \t."
269-
self.assertTStringEqual(
270-
t, ("Tab: ", r". Raw tab: \t."), [(tab, "tab")]
271-
)
272-
self.assertEqual(fstring(t), "Tab: \t. Raw tab: \\t.")
273-
274-
# Test concatenation with raw f-string literal
275-
tab = '\t'
276-
t = t"Tab: {tab}. " rf"f-tab: {tab}. Raw tab: \t."
277-
self.assertTStringEqual(
278-
t, ("Tab: ", ". f-tab: \t. Raw tab: \\t."), [(tab, "tab")]
279-
)
280-
self.assertEqual(fstring(t), "Tab: \t. f-tab: \t. Raw tab: \\t.")
281-
245+
# Test disallowed mix of t-string and string/f-string (incl. bytes)
282246
what = 't'
283-
expected_msg = 'cannot mix bytes and nonbytes literals'
247+
expected_msg = 'cannot mix t-string literals with string or bytes literals'
284248
for case in (
249+
"t'{what}-string literal' 'str literal'",
250+
"t'{what}-string literal' u'unicode literal'",
251+
"t'{what}-string literal' f'f-string literal'",
252+
"t'{what}-string literal' r'raw string literal'",
253+
"t'{what}-string literal' rf'raw f-string literal'",
285254
"t'{what}-string literal' b'bytes literal'",
286255
"t'{what}-string literal' br'raw bytes literal'",
256+
"'str literal' t'{what}-string literal'",
257+
"u'unicode literal' t'{what}-string literal'",
258+
"f'f-string literal' t'{what}-string literal'",
259+
"r'raw string literal' t'{what}-string literal'",
260+
"rf'raw f-string literal' t'{what}-string literal'",
261+
"b'bytes literal' t'{what}-string literal'",
262+
"br'raw bytes literal' t'{what}-string literal'",
287263
):
288-
with self.assertRaisesRegex(SyntaxError, expected_msg):
289-
eval(case)
264+
with self.subTest(case):
265+
with self.assertRaisesRegex(SyntaxError, expected_msg):
266+
eval(case)
290267

291268
def test_triple_quoted(self):
292269
# Test triple-quoted t-strings

Lib/test/test_unparse.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,6 @@ def test_tstrings(self):
206206
self.check_ast_roundtrip("t'foo'")
207207
self.check_ast_roundtrip("t'foo {bar}'")
208208
self.check_ast_roundtrip("t'foo {bar!s:.2f}'")
209-
self.check_ast_roundtrip("t'foo {bar}' f'{bar}'")
210-
self.check_ast_roundtrip("f'{bar}' t'foo {bar}'")
211-
self.check_ast_roundtrip("t'foo {bar}' fr'\\hello {bar}'")
212-
self.check_ast_roundtrip("t'foo {bar}' u'bar'")
213209

214210
def test_strings(self):
215211
self.check_ast_roundtrip("u'foo'")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Reflect recent :pep:`750` change.
2+
3+
Disallow concatenation of ``string.templatelib.Template`` and :class:`str`.
4+
Also, disallow implicit concatenation of t-string literals with string or
5+
f-string literals.

Objects/templateobject.c

Lines changed: 7 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ templateiter_next(PyObject *op)
3030
Py_SETREF(item, PyIter_Next(self->interpolationsiter));
3131
self->from_strings = 1;
3232
}
33-
} else {
33+
}
34+
else {
3435
item = PyIter_Next(self->interpolationsiter);
3536
self->from_strings = 1;
3637
}
@@ -245,54 +246,6 @@ template_iter(PyObject *op)
245246
return (PyObject *)iter;
246247
}
247248

248-
static PyObject *
249-
template_strings_append_str(PyObject *strings, PyObject *str)
250-
{
251-
Py_ssize_t stringslen = PyTuple_GET_SIZE(strings);
252-
PyObject *string = PyTuple_GET_ITEM(strings, stringslen - 1);
253-
PyObject *concat = PyUnicode_Concat(string, str);
254-
if (concat == NULL) {
255-
return NULL;
256-
}
257-
258-
PyObject *newstrings = PyTuple_New(stringslen);
259-
if (newstrings == NULL) {
260-
Py_DECREF(concat);
261-
return NULL;
262-
}
263-
264-
for (Py_ssize_t i = 0; i < stringslen - 1; i++) {
265-
PyTuple_SET_ITEM(newstrings, i, Py_NewRef(PyTuple_GET_ITEM(strings, i)));
266-
}
267-
PyTuple_SET_ITEM(newstrings, stringslen - 1, concat);
268-
269-
return newstrings;
270-
}
271-
272-
static PyObject *
273-
template_strings_prepend_str(PyObject *strings, PyObject *str)
274-
{
275-
Py_ssize_t stringslen = PyTuple_GET_SIZE(strings);
276-
PyObject *string = PyTuple_GET_ITEM(strings, 0);
277-
PyObject *concat = PyUnicode_Concat(str, string);
278-
if (concat == NULL) {
279-
return NULL;
280-
}
281-
282-
PyObject *newstrings = PyTuple_New(stringslen);
283-
if (newstrings == NULL) {
284-
Py_DECREF(concat);
285-
return NULL;
286-
}
287-
288-
PyTuple_SET_ITEM(newstrings, 0, concat);
289-
for (Py_ssize_t i = 1; i < stringslen; i++) {
290-
PyTuple_SET_ITEM(newstrings, i, Py_NewRef(PyTuple_GET_ITEM(strings, i)));
291-
}
292-
293-
return newstrings;
294-
}
295-
296249
static PyObject *
297250
template_strings_concat(PyObject *left, PyObject *right)
298251
{
@@ -344,47 +297,17 @@ template_concat_templates(templateobject *self, templateobject *other)
344297
return newtemplate;
345298
}
346299

347-
static PyObject *
348-
template_concat_template_str(templateobject *self, PyObject *other)
349-
{
350-
PyObject *newstrings = template_strings_append_str(self->strings, other);
351-
if (newstrings == NULL) {
352-
return NULL;
353-
}
354-
355-
PyObject *newtemplate = _PyTemplate_Build(newstrings, self->interpolations);
356-
Py_DECREF(newstrings);
357-
return newtemplate;
358-
}
359-
360-
static PyObject *
361-
template_concat_str_template(templateobject *self, PyObject *other)
362-
{
363-
PyObject *newstrings = template_strings_prepend_str(self->strings, other);
364-
if (newstrings == NULL) {
365-
return NULL;
366-
}
367-
368-
PyObject *newtemplate = _PyTemplate_Build(newstrings, self->interpolations);
369-
Py_DECREF(newstrings);
370-
return newtemplate;
371-
}
372-
373300
PyObject *
374301
_PyTemplate_Concat(PyObject *self, PyObject *other)
375302
{
376303
if (_PyTemplate_CheckExact(self) && _PyTemplate_CheckExact(other)) {
377304
return template_concat_templates((templateobject *) self, (templateobject *) other);
378305
}
379-
else if ((_PyTemplate_CheckExact(self)) && PyUnicode_Check(other)) {
380-
return template_concat_template_str((templateobject *) self, other);
381-
}
382-
else if (PyUnicode_Check(self) && (_PyTemplate_CheckExact(other))) {
383-
return template_concat_str_template((templateobject *) other, self);
384-
}
385-
else {
386-
Py_RETURN_NOTIMPLEMENTED;
387-
}
306+
307+
PyErr_Format(PyExc_TypeError,
308+
"can only concatenate string.templatelib.Template (not \"%T\") to string.templatelib.Template",
309+
other);
310+
return NULL;
388311
}
389312

390313
static PyObject *

Objects/unicodeobject.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
5656
#include "pycore_pyhash.h" // _Py_HashSecret_t
5757
#include "pycore_pylifecycle.h" // _Py_SetFileSystemEncoding()
5858
#include "pycore_pystate.h" // _PyInterpreterState_GET()
59-
#include "pycore_template.h" // _PyTemplate_Concat()
6059
#include "pycore_tuple.h" // _PyTuple_FromArray()
6160
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
6261
#include "pycore_unicodeobject.h" // struct _Py_unicode_state
@@ -11639,16 +11638,10 @@ PyUnicode_Concat(PyObject *left, PyObject *right)
1163911638
return NULL;
1164011639

1164111640
if (!PyUnicode_Check(right)) {
11642-
if (_PyTemplate_CheckExact(right)) {
11643-
// str + tstring is implemented in the tstring type
11644-
return _PyTemplate_Concat(left, right);
11645-
}
11646-
else {
11647-
PyErr_Format(PyExc_TypeError,
11648-
"can only concatenate str (not \"%.200s\") to str",
11649-
Py_TYPE(right)->tp_name);
11650-
return NULL;
11651-
}
11641+
PyErr_Format(PyExc_TypeError,
11642+
"can only concatenate str (not \"%.200s\") to str",
11643+
Py_TYPE(right)->tp_name);
11644+
return NULL;
1165211645
}
1165311646

1165411647
/* Shortcuts */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy