From 74b205b3eb6432d81d745da1dae4359e42ae76d4 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
 <31488909+miss-islington@users.noreply.github.com>
Date: Tue, 24 May 2022 23:32:20 -0700
Subject: [PATCH] gh-92728: Restore re.template, but deprecate it (GH-93161)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Revert "bpo-47211: Remove function re.template() and flag re.TEMPLATE (GH-32300)"

This reverts commit b09184bf05b07b77c5ecfedd4daa846be3cbf0a9.
(cherry picked from commit 16a7e4a0b75080275bf12cfb71d54b01d85099b2)

Co-authored-by: Miro Hrončok <miro@hroncok.cz>
---
 Doc/whatsnew/3.11.rst                         |  5 ++++
 Lib/re/__init__.py                            | 23 +++++++++++++-
 Lib/re/_compiler.py                           |  2 ++
 Lib/re/_constants.py                          |  1 +
 Lib/re/_parser.py                             |  3 +-
 Lib/test/test_re.py                           | 30 +++++++++++++++++--
 Misc/NEWS.d/3.11.0b1.rst                      |  1 +
 ...2-05-24-10-59-02.gh-issue-92728.zxTifq.rst |  3 ++
 Modules/_sre/sre.c                            |  1 +
 Modules/_sre/sre_constants.h                  |  1 +
 10 files changed, 65 insertions(+), 5 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2022-05-24-10-59-02.gh-issue-92728.zxTifq.rst

diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 12b4c22d548..c7ff6ca4938 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -1273,6 +1273,11 @@ Deprecated
   is now deprecated. Support will be removed in Python 3.13. (Contributed by
   Jingchen Ye in :gh:`90224`.)
 
+* The :func:`re.template` function and the corresponding :const:`re.TEMPLATE`
+  and :const:`re.T` flags are deprecated, as they were undocumented and
+  lacked an obvious purpose. They will be removed in Python 3.13.
+  (Contributed by Serhiy Storchaka and Miro Hrončok in :gh:`92728`.)
+
 
 Pending Removal in Python 3.12
 ==============================
diff --git a/Lib/re/__init__.py b/Lib/re/__init__.py
index c9b511422f1..d58c2117ef3 100644
--- a/Lib/re/__init__.py
+++ b/Lib/re/__init__.py
@@ -129,7 +129,7 @@
 # public symbols
 __all__ = [
     "match", "fullmatch", "search", "sub", "subn", "split",
-    "findall", "finditer", "compile", "purge", "escape",
+    "findall", "finditer", "compile", "purge", "template", "escape",
     "error", "Pattern", "Match", "A", "I", "L", "M", "S", "X", "U",
     "ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
     "UNICODE", "NOFLAG", "RegexFlag",
@@ -148,6 +148,8 @@ class RegexFlag:
     MULTILINE = M = _compiler.SRE_FLAG_MULTILINE # make anchors look for newline
     DOTALL = S = _compiler.SRE_FLAG_DOTALL # make dot match newline
     VERBOSE = X = _compiler.SRE_FLAG_VERBOSE # ignore whitespace and comments
+    # sre extensions (experimental, don't rely on these)
+    TEMPLATE = T = _compiler.SRE_FLAG_TEMPLATE # unknown purpose, deprecated
     DEBUG = _compiler.SRE_FLAG_DEBUG # dump pattern after compilation
     __str__ = object.__str__
     _numeric_repr_ = hex
@@ -229,6 +231,18 @@ def purge():
     _cache.clear()
     _compile_repl.cache_clear()
 
+def template(pattern, flags=0):
+    "Compile a template pattern, returning a Pattern object, deprecated"
+    import warnings
+    warnings.warn("The re.template() function is deprecated "
+                  "as it is an undocumented function "
+                  "without an obvious purpose. "
+                  "Use re.compile() instead.",
+                  DeprecationWarning)
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)  # warn just once
+        return _compile(pattern, flags|T)
+
 # SPECIAL_CHARS
 # closing ')', '}' and ']'
 # '-' (a range in character set)
@@ -270,6 +284,13 @@ def _compile(pattern, flags):
         return pattern
     if not _compiler.isstring(pattern):
         raise TypeError("first argument must be string or compiled pattern")
+    if flags & T:
+        import warnings
+        warnings.warn("The re.TEMPLATE/re.T flag is deprecated "
+                  "as it is an undocumented flag "
+                  "without an obvious purpose. "
+                  "Don't use it.",
+                  DeprecationWarning)
     p = _compiler.compile(pattern, flags)
     if not (flags & DEBUG):
         if len(_cache) >= _MAXCACHE:
diff --git a/Lib/re/_compiler.py b/Lib/re/_compiler.py
index 63d82025505..4b5322338cb 100644
--- a/Lib/re/_compiler.py
+++ b/Lib/re/_compiler.py
@@ -108,6 +108,8 @@ def _compile(data, pattern, flags):
             else:
                 emit(ANY)
         elif op in REPEATING_CODES:
+            if flags & SRE_FLAG_TEMPLATE:
+                raise error("internal: unsupported template operator %r" % (op,))
             if _simple(av[2]):
                 emit(REPEATING_CODES[op][2])
                 skip = _len(code); emit(0)
diff --git a/Lib/re/_constants.py b/Lib/re/_constants.py
index 71204d903b3..1cc85c631f2 100644
--- a/Lib/re/_constants.py
+++ b/Lib/re/_constants.py
@@ -204,6 +204,7 @@ def _makecodes(*names):
 }
 
 # flags
+SRE_FLAG_TEMPLATE = 1 # template mode (unknown purpose, deprecated)
 SRE_FLAG_IGNORECASE = 2 # case insensitive
 SRE_FLAG_LOCALE = 4 # honour system locale
 SRE_FLAG_MULTILINE = 8 # treat target as multiline string
diff --git a/Lib/re/_parser.py b/Lib/re/_parser.py
index a393c508d86..f747a0396b1 100644
--- a/Lib/re/_parser.py
+++ b/Lib/re/_parser.py
@@ -61,11 +61,12 @@
     "x": SRE_FLAG_VERBOSE,
     # extensions
     "a": SRE_FLAG_ASCII,
+    "t": SRE_FLAG_TEMPLATE,
     "u": SRE_FLAG_UNICODE,
 }
 
 TYPE_FLAGS = SRE_FLAG_ASCII | SRE_FLAG_LOCALE | SRE_FLAG_UNICODE
-GLOBAL_FLAGS = SRE_FLAG_DEBUG
+GLOBAL_FLAGS = SRE_FLAG_DEBUG | SRE_FLAG_TEMPLATE
 
 class State:
     # keeps track of state for parsing
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index 442547da31d..6d61412f160 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -2417,6 +2417,30 @@ def test_bug_gh91616(self):
         self.assertTrue(re.fullmatch(r'(?s:(?>.*?\.).*)\Z', "a.txt")) # reproducer
         self.assertTrue(re.fullmatch(r'(?s:(?=(?P<g0>.*?\.))(?P=g0).*)\Z', "a.txt"))
 
+    def test_template_function_and_flag_is_deprecated(self):
+        with self.assertWarns(DeprecationWarning) as cm:
+            template_re1 = re.template(r'a')
+        self.assertIn('re.template()', str(cm.warning))
+        self.assertIn('is deprecated', str(cm.warning))
+        self.assertIn('function', str(cm.warning))
+        self.assertNotIn('flag', str(cm.warning))
+
+        with self.assertWarns(DeprecationWarning) as cm:
+            # we deliberately use more flags here to test that that still
+            # triggers the warning
+            # if paranoid, we could test multiple different combinations,
+            # but it's probably not worth it
+            template_re2 = re.compile(r'a', flags=re.TEMPLATE|re.UNICODE)
+        self.assertIn('re.TEMPLATE', str(cm.warning))
+        self.assertIn('is deprecated', str(cm.warning))
+        self.assertIn('flag', str(cm.warning))
+        self.assertNotIn('function', str(cm.warning))
+
+        # while deprecated, is should still function
+        self.assertEqual(template_re1, template_re2)
+        self.assertTrue(template_re1.match('ahoy'))
+        self.assertFalse(template_re1.match('nope'))
+
 
 def get_debug_out(pat):
     with captured_stdout() as out:
@@ -2611,11 +2635,11 @@ def test_flags_repr(self):
                          "re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000")
         self.assertEqual(
                 repr(~re.I),
-                "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.DEBUG|0x1")
+                "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.TEMPLATE|re.DEBUG")
         self.assertEqual(repr(~(re.I|re.S|re.X)),
-                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DEBUG|0x1")
+                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG")
         self.assertEqual(repr(~(re.I|re.S|re.X|(1<<20))),
-                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DEBUG|0xffe01")
+                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG|0xffe00")
 
 
 class ImplementationTest(unittest.TestCase):
diff --git a/Misc/NEWS.d/3.11.0b1.rst b/Misc/NEWS.d/3.11.0b1.rst
index 0def806185e..c135eff4598 100644
--- a/Misc/NEWS.d/3.11.0b1.rst
+++ b/Misc/NEWS.d/3.11.0b1.rst
@@ -1373,6 +1373,7 @@ Suppress expression chaining for more :mod:`re` parsing errors.
 
 Remove undocumented and never working function ``re.template()`` and flag
 ``re.TEMPLATE``.
+This was later reverted in 3.11.0b2 and deprecated instead.
 
 ..
 
diff --git a/Misc/NEWS.d/next/Library/2022-05-24-10-59-02.gh-issue-92728.zxTifq.rst b/Misc/NEWS.d/next/Library/2022-05-24-10-59-02.gh-issue-92728.zxTifq.rst
new file mode 100644
index 00000000000..b39609be2c4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-05-24-10-59-02.gh-issue-92728.zxTifq.rst
@@ -0,0 +1,3 @@
+The :func:`re.template` function and the corresponding :const:`re.TEMPLATE`
+and :const:`re.T` flags are restored after they were removed in 3.11.0b1,
+but they are now deprecated, so they might be removed from Python 3.13.
diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c
index bd9204da428..491734f2438 100644
--- a/Modules/_sre/sre.c
+++ b/Modules/_sre/sre.c
@@ -1323,6 +1323,7 @@ pattern_repr(PatternObject *obj)
         const char *name;
         int value;
     } flag_names[] = {
+        {"re.TEMPLATE", SRE_FLAG_TEMPLATE},
         {"re.IGNORECASE", SRE_FLAG_IGNORECASE},
         {"re.LOCALE", SRE_FLAG_LOCALE},
         {"re.MULTILINE", SRE_FLAG_MULTILINE},
diff --git a/Modules/_sre/sre_constants.h b/Modules/_sre/sre_constants.h
index d5de650b702..590d5be7cb4 100644
--- a/Modules/_sre/sre_constants.h
+++ b/Modules/_sre/sre_constants.h
@@ -85,6 +85,7 @@
 #define SRE_CATEGORY_UNI_NOT_WORD 15
 #define SRE_CATEGORY_UNI_LINEBREAK 16
 #define SRE_CATEGORY_UNI_NOT_LINEBREAK 17
+#define SRE_FLAG_TEMPLATE 1
 #define SRE_FLAG_IGNORECASE 2
 #define SRE_FLAG_LOCALE 4
 #define SRE_FLAG_MULTILINE 8
-- 
GitLab