]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-145239: Accept unary plus literal pattern (#148566)
authorBartosz Sławecki <bartosz@ilikepython.com>
Thu, 23 Apr 2026 19:07:28 +0000 (21:07 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Apr 2026 19:07:28 +0000 (22:07 +0300)
Add '+' alternatives to signed_number and signed_real_number grammar
rules, mirroring how unary minus is already handled for pattern matching.
Unary plus is a no-op on numbers so the value is returned directly without
wrapping in a UnaryOp node.

Doc/reference/compound_stmts.rst
Doc/whatsnew/3.15.rst
Grammar/python.gram
Lib/test/.ruff.toml
Lib/test/test_patma.py
Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-23-21-45.gh-issue-145239.pL8qRt.rst [new file with mode: 0644]
Parser/parser.c

index 0cf0a41bfb400c6c5b373c13a7adb73c21e9822b..72e1cad3bbd8924adc6cbbecbbe3b26f848145b6 100644 (file)
@@ -858,7 +858,7 @@ A literal pattern corresponds to most
                   : | "None"
                   : | "True"
                   : | "False"
-   signed_number: ["-"] NUMBER
+   signed_number: ["+" | "-"] NUMBER
 
 The rule ``strings`` and the token ``NUMBER`` are defined in the
 :doc:`standard Python grammar <./grammar>`.  Triple-quoted strings are
index 500797910edb90a218379ce09e3c6abcaddce01e..dbdd5de01700a3f074185d616198aea272a98498 100644 (file)
@@ -645,6 +645,10 @@ Other language changes
 * Allow the *count* argument of :meth:`bytes.replace` to be a keyword.
   (Contributed by Stan Ulbrych in :gh:`147856`.)
 
+* Unary plus is now accepted in :keyword:`match` literal patterns, mirroring
+  the existing support for unary minus.
+  (Contributed by Bartosz Sławecki in :gh:`145239`.)
+
 
 New modules
 ===========
index 3a91d426c36501923f1dae938bd63ee62b0cb810..9bf3a67939fcf37f911266f2c07c1800bb77ebb1 100644 (file)
@@ -554,10 +554,12 @@ complex_number[expr_ty]:
 
 signed_number[expr_ty]:
     | NUMBER
+    | '+' number=NUMBER { number }
     | '-' number=NUMBER { _PyAST_UnaryOp(USub, number, EXTRA) }
 
 signed_real_number[expr_ty]:
     | real_number
+    | '+' real=real_number { real }
     | '-' real=real_number { _PyAST_UnaryOp(USub, real, EXTRA) }
 
 real_number[expr_ty]:
@@ -565,6 +567,7 @@ real_number[expr_ty]:
 
 imaginary_number[expr_ty]:
     | imag=NUMBER { _PyPegen_ensure_imaginary(p, imag) }
+    | '+' imag=NUMBER { _PyPegen_ensure_imaginary(p, imag) }
 
 capture_pattern[pattern_ty]:
     | target=pattern_capture_target { _PyAST_MatchAs(NULL, target->v.Name.id, EXTRA) }
index a960543f277935b489d65836224184b8b4092862..dca74eb6e14bbd01db3eca24becb5319177cdc96 100644 (file)
@@ -18,6 +18,8 @@ extend-exclude = [
     "test_lazy_import/__init__.py",
     "test_lazy_import/data/*.py",
     "test_lazy_import/data/**/*.py",
+    # Unary plus literal pattern is not yet supported by Ruff (GH-145239)
+    "test_patma.py",
 ]
 
 [lint]
index 5d0857b059ea234fa69e1b34ec66f25c01bee2d8..29cce4ee6d271ff5711a288537d23c4e26821516 100644 (file)
@@ -2762,6 +2762,96 @@ class TestPatma(unittest.TestCase):
         self.assertEqual(y, 1)
         self.assertIs(z, x)
 
+    def test_patma_256(self):
+        x = 0
+        match x:
+            case +0:
+                y = 0
+        self.assertEqual(x, 0)
+        self.assertEqual(y, 0)
+
+    def test_patma_257(self):
+        x = 0
+        match x:
+            case +0.0:
+                y = 0
+        self.assertEqual(x, 0)
+        self.assertEqual(y, 0)
+
+    def test_patma_258(self):
+        x = 0
+        match x:
+            case +0j:
+                y = 0
+        self.assertEqual(x, 0)
+        self.assertEqual(y, 0)
+
+    def test_patma_259(self):
+        x = 0
+        match x:
+            case +0.0j:
+                y = 0
+        self.assertEqual(x, 0)
+        self.assertEqual(y, 0)
+
+    def test_patma_260(self):
+        x = 1
+        match x:
+            case +1:
+                y = 0
+        self.assertEqual(x, 1)
+        self.assertEqual(y, 0)
+
+    def test_patma_261(self):
+        x = 1.5
+        match x:
+            case +1.5:
+                y = 0
+        self.assertEqual(x, 1.5)
+        self.assertEqual(y, 0)
+
+    def test_patma_262(self):
+        x = 1j
+        match x:
+            case +1j:
+                y = 0
+        self.assertEqual(x, 1j)
+        self.assertEqual(y, 0)
+
+    def test_patma_263(self):
+        x = 1.5j
+        match x:
+            case +1.5j:
+                y = 0
+        self.assertEqual(x, 1.5j)
+        self.assertEqual(y, 0)
+
+    def test_patma_264(self):
+        x = 0.25 + 1.75j
+        match x:
+            case +0.25 + 1.75j:
+                y = 0
+        self.assertEqual(x, 0.25 + 1.75j)
+        self.assertEqual(y, 0)
+
+    def test_patma_265(self):
+        x = 0.25 - 1.75j
+        match x:
+            case 0.25 - +1.75j:
+                y = 0
+        self.assertEqual(x, 0.25 - 1.75j)
+        self.assertEqual(y, 0)
+
+    def test_patma_266(self):
+        x = 0
+        match x:
+            case +1e1000:
+                y = 0
+            case 0:
+                y = 1
+        self.assertEqual(x, 0)
+        self.assertEqual(y, 1)
+
     def test_patma_runtime_checkable_protocol(self):
         # Runtime-checkable protocol
         from typing import Protocol, runtime_checkable
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-23-21-45.gh-issue-145239.pL8qRt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-23-21-45.gh-issue-145239.pL8qRt.rst
new file mode 100644 (file)
index 0000000..282b991
--- /dev/null
@@ -0,0 +1,3 @@
+Unary plus is now accepted in :keyword:`match` literal patterns, mirroring
+the existing support for unary minus.
+Patch by Bartosz Sławecki.
index f853d309de9180ef42d2e47bec9ffe39bd57801f..c55c081dfc3d8e21cf3a4e42ef13b8289b71ef97 100644 (file)
@@ -9066,7 +9066,7 @@ complex_number_rule(Parser *p)
     return _res;
 }
 
-// signed_number: NUMBER | '-' NUMBER
+// signed_number: NUMBER | '+' NUMBER | '-' NUMBER
 static expr_ty
 signed_number_rule(Parser *p)
 {
@@ -9107,6 +9107,33 @@ signed_number_rule(Parser *p)
         D(fprintf(stderr, "%*c%s signed_number[%d-%d]: %s failed!\n", p->level, ' ',
                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NUMBER"));
     }
+    { // '+' NUMBER
+        if (p->error_indicator) {
+            p->level--;
+            return NULL;
+        }
+        D(fprintf(stderr, "%*c> signed_number[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+' NUMBER"));
+        Token * _literal;
+        expr_ty number;
+        if (
+            (_literal = _PyPegen_expect_token(p, 14))  // token='+'
+            &&
+            (number = _PyPegen_number_token(p))  // NUMBER
+        )
+        {
+            D(fprintf(stderr, "%*c+ signed_number[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+' NUMBER"));
+            _res = number;
+            if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
+                p->error_indicator = 1;
+                p->level--;
+                return NULL;
+            }
+            goto done;
+        }
+        p->mark = _mark;
+        D(fprintf(stderr, "%*c%s signed_number[%d-%d]: %s failed!\n", p->level, ' ',
+                  p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+' NUMBER"));
+    }
     { // '-' NUMBER
         if (p->error_indicator) {
             p->level--;
@@ -9149,7 +9176,7 @@ signed_number_rule(Parser *p)
     return _res;
 }
 
-// signed_real_number: real_number | '-' real_number
+// signed_real_number: real_number | '+' real_number | '-' real_number
 static expr_ty
 signed_real_number_rule(Parser *p)
 {
@@ -9190,6 +9217,33 @@ signed_real_number_rule(Parser *p)
         D(fprintf(stderr, "%*c%s signed_real_number[%d-%d]: %s failed!\n", p->level, ' ',
                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "real_number"));
     }
+    { // '+' real_number
+        if (p->error_indicator) {
+            p->level--;
+            return NULL;
+        }
+        D(fprintf(stderr, "%*c> signed_real_number[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+' real_number"));
+        Token * _literal;
+        expr_ty real;
+        if (
+            (_literal = _PyPegen_expect_token(p, 14))  // token='+'
+            &&
+            (real = real_number_rule(p))  // real_number
+        )
+        {
+            D(fprintf(stderr, "%*c+ signed_real_number[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+' real_number"));
+            _res = real;
+            if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
+                p->error_indicator = 1;
+                p->level--;
+                return NULL;
+            }
+            goto done;
+        }
+        p->mark = _mark;
+        D(fprintf(stderr, "%*c%s signed_real_number[%d-%d]: %s failed!\n", p->level, ' ',
+                  p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+' real_number"));
+    }
     { // '-' real_number
         if (p->error_indicator) {
             p->level--;
@@ -9275,7 +9329,7 @@ real_number_rule(Parser *p)
     return _res;
 }
 
-// imaginary_number: NUMBER
+// imaginary_number: NUMBER | '+' NUMBER
 static expr_ty
 imaginary_number_rule(Parser *p)
 {
@@ -9312,6 +9366,33 @@ imaginary_number_rule(Parser *p)
         D(fprintf(stderr, "%*c%s imaginary_number[%d-%d]: %s failed!\n", p->level, ' ',
                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NUMBER"));
     }
+    { // '+' NUMBER
+        if (p->error_indicator) {
+            p->level--;
+            return NULL;
+        }
+        D(fprintf(stderr, "%*c> imaginary_number[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+' NUMBER"));
+        Token * _literal;
+        expr_ty imag;
+        if (
+            (_literal = _PyPegen_expect_token(p, 14))  // token='+'
+            &&
+            (imag = _PyPegen_number_token(p))  // NUMBER
+        )
+        {
+            D(fprintf(stderr, "%*c+ imaginary_number[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+' NUMBER"));
+            _res = _PyPegen_ensure_imaginary ( p , imag );
+            if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
+                p->error_indicator = 1;
+                p->level--;
+                return NULL;
+            }
+            goto done;
+        }
+        p->mark = _mark;
+        D(fprintf(stderr, "%*c%s imaginary_number[%d-%d]: %s failed!\n", p->level, ' ',
+                  p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+' NUMBER"));
+    }
     _res = NULL;
   done:
     p->level--;