PEP 653: Rephrase translations in terms of failure, rather than success, to make them easier to compose. (#1865)

This commit is contained in:
Mark Shannon 2021-03-10 15:22:07 +00:00 committed by GitHub
parent c91e284b01
commit 2aec45c8b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 223 additions and 155 deletions

View File

@ -160,7 +160,8 @@ Semantics of the matching process
In the following, all variables of the form ``$var`` are temporary variables and are not visible to the Python program.
They may be visible via introspection, but that is an implementation detail and should not be relied on.
The psuedo-statement ``DONE`` is used to signify that matching is complete and that following patterns should be ignored.
The psuedo-statement ``FAIL`` is used to signify that matching failed for this pattern and that matching should move to the next pattern.
If control reaches the end of the translation without reaching a ``FAIL``, then it has matched, and following patterns are ignored.
All the translations below include guards. If no guard is present, simply substitute the guard ``if True`` when translating.
Variables of the form ``$ALL_CAPS`` are meta-variables holding a syntactic element, they are not normal variables.
@ -197,57 +198,55 @@ In addition some helper variables are initialized::
Capture patterns
''''''''''''''''
Capture patterns always match, so::
Capture patterns always match, so the irrefutable match::
case capture_var if guard:
case capture_var:
translates to::
capture_var = $value
if guard:
DONE
Wildcard patterns
'''''''''''''''''
Wildcard patterns always match, so::
case _ if guard:
case _:
translates to::
if guard:
DONE
# No code -- Automatically matches
Literal Patterns
''''''''''''''''
The literal pattern::
case LITERAL if guard:
case LITERAL:
translates to::
if $value == LITERAL and guard:
DONE
if $value != LITERAL:
FAIL
except when the literal is one of ``None``, ``True`` or ``False`` ,
when it translates to::
if $value is LITERAL and guard:
DONE
if $value is not LITERAL:
FAIL
Value Patterns
''''''''''''''
The value pattern::
case value.pattern if guard:
case value.pattern:
translates to::
if $value == value.pattern and guard:
DONE
if $value != value.pattern:
FAIL
Sequence Patterns
'''''''''''''''''
@ -257,33 +256,33 @@ Before matching the first sequence pattern, but after checking that ``$value`` i
A pattern not including a star pattern::
case [$VARS] if guard:
case [$VARS]:
translates to::
if $kind & MATCH_SEQUENCE:
if $list is None:
$list = list($value)
if len($list) == len($VARS):
$VARS = $list
if guard:
DONE
if $kind & MATCH_SEQUENCE == 0:
FAIL
if $list is None:
$list = list($value)
if len($list) != len($VARS):
FAIL
$VARS = $list
Example: [2]_
A pattern including a star pattern::
case [$VARS] if guard
case [$VARS]
translates to::
if $kind & MATCH_SEQUENCE:
if $list is None:
$list = list($value)
if len($list) >= len($VARS):
$VARS = $list # Note that $VARS includes a star expression.
if guard:
DONE
if $kind & MATCH_SEQUENCE == 0:
FAIL
if $list is None:
$list = list($value)
if len($list) < len($VARS):
FAIL
$VARS = $list # Note that $VARS includes a star expression.
Example: [3]_
@ -295,38 +294,39 @@ Before matching the first mapping pattern, but after checking that ``$value`` is
A pattern not including a double-star pattern::
case {$KEYWORD_PATTERNS} if guard:
case {$KEYWORD_PATTERNS}:
translates to::
if $kind & MATCH_MAPPING:
if $dict is None:
$dict = dict($value)
if $dict.keys() == $KEYWORD_PATTERNS.keys():
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $dict[QUOTE($KEYWORD)]
if guard:
DONE
if $kind & MATCH_MAPPING == 0:
FAIL
if $dict is None:
$dict = dict($value)
if $dict.keys() != $KEYWORD_PATTERNS.keys():
FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $dict[QUOTE($KEYWORD)]
Example: [4]_
A pattern including a double-star pattern::
case {$KEYWORD_PATTERNS, **$DOUBLE_STARRED_PATTERN} if guard::
case {$KEYWORD_PATTERNS, **$DOUBLE_STARRED_PATTERN}:
translates to::
if $kind & MATCH_MAPPING:
if $dict is None:
$dict = dict($value)
if $dict.keys() >= $KEYWORD_PATTERNS.keys():
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
$tmp = dict($dict)
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $tmp.pop(QUOTE($KEYWORD))
$DOUBLE_STARRED_PATTERN = $tmp
DONE
if $kind & MATCH_MAPPING == 0:
FAIL
if $dict is None:
$dict = dict($value)
if $dict.keys() not >= $KEYWORD_PATTERNS.keys():
FAIL:
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
$tmp = dict($dict)
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $tmp.pop(QUOTE($KEYWORD))
$DOUBLE_STARRED_PATTERN = $tmp
Example: [5]_
@ -335,46 +335,45 @@ Class Patterns
Class pattern with no arguments::
match ClsName() if guard:
case ClsName():
translates to::
if $kind & MATCH_CLASS:
if isinstance($value, ClsName):
if guard:
DONE
if $kind & (MATCH_CLASS | MATCH_DEFAULT) == 0:
FAIL
if not isinstance($value, ClsName):
FAIL
Class pattern with a single positional pattern::
match ClsName($PATTERN) if guard:
case ClsName($VAR):
translates to::
if $kind & MATCH_SELF:
if isinstance($value, ClsName):
x = $value
if guard:
DONE
if not isinstance($value, ClsName):
FAIL
$VAR = $value
else:
As other positional-only class pattern
Positional-only class pattern::
match ClsName($VARS) if guard:
case ClsName($VARS):
translates to::
if $kind & MATCH_CLASS:
if isinstance($value, ClsName):
if $items is None:
$items = type($value).__deconstruct__($value)
# $VARS is a meta-variable.
if len($items) == len($VARS):
$VARS = $items
if guard:
DONE
if $kind & MATCH_CLASS == 0:
FAIL
if not isinstance($value, ClsName):
FAIL
if $items is None:
$items = type($value).__deconstruct__($value)
# $VARS is a meta-variable.
if len($items) != len($VARS):
FAIL
$VARS = $items
.. note::
@ -384,45 +383,96 @@ translates to::
Class patterns with keyword patterns::
match ClsName($VARS, $KEYWORD_PATTERNS) if guard:
case ClsName($VARS, $KEYWORD_PATTERNS):
translates to::
if $kind & MATCH_CLASS:
if isinstance($value, ClsName):
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
$right_attrs = attrs[len($VARS):]
if set($right_attrs) >= set($KEYWORD_PATTERNS):
$VARS = items[:len($VARS)]
for $KEYWORD in $KEYWORD_PATTERNS:
$index = $attrs.index(QUOTE($KEYWORD))
$KEYWORD_PATTERNS[$KEYWORD] = $items[$index]
if guard:
DONE
if $kind & MATCH_CLASS == 0:
FAIL
if not isinstance($value, ClsName):
FAIL
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
$right_attrs = attrs[len($VARS):]
if not set($right_attrs) >= set($KEYWORD_PATTERNS):
FAIL
$VARS = items[:len($VARS)]
for $KEYWORD in $KEYWORD_PATTERNS:
$index = $attrs.index(QUOTE($KEYWORD))
$KEYWORD_PATTERNS[$KEYWORD] = $items[$index]
Example: [6]_
Class patterns with all keyword patterns::
match ClsName($KEYWORD_PATTERNS) if guard:
case ClsName($KEYWORD_PATTERNS):
translates to::
if $kind & MATCH_CLASS:
As above with $VARS == ()
elif $kind & MATCH_DEFAULT:
if isinstance($value, ClsName) and hasattr($value, "__dict__"):
if $value.__dict__.keys() >= set($KEYWORD_PATTERNS):
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $value.__dict__[QUOTE($KEYWORD)]
if guard:
DONE
if not isinstance($value, ClsName):
FAIL
if not hasattr($value, "__dict__"):
FAIL
if not $value.__dict__.keys() >= set($KEYWORD_PATTERNS):
FAIL
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $value.__dict__[QUOTE($KEYWORD)]
else:
FAIL
Example: [7]_
Nested patterns
'''''''''''''''
The above specification assumes that patterns are not nested. For nested patterns
the above translations are applied recursively by introducing temporary capture patterns.
For example, the pattern::
case [int(), str()]:
translates to::
if $kind & MATCH_SEQUENCE == 0:
FAIL
if $list is None:
$list = list($value)
if len($list) != 2:
FAIL
$value_0, $value_1 = $list
#Now match on temporary values
$kind_0 = type($value_0).__match_kind__
if $kind_0 & (MATCH_CLASS | MATCH_DEFAULT) == 0:
FAIL
if not isinstance($value_0, int):
FAIL
$kind_1 = type($value_1).__match_kind__
if $kind_1 & (MATCH_CLASS | MATCH_DEFAULT) == 0:
FAIL
if not isinstance($value_1, str):
FAIL
Guards
''''''
Guards translate to a test following the rest of the translation::
case pattern if guard:
translates to::
[translation for pattern]
if not guard:
FAIL
Non-conforming ``__match_kind__``
'''''''''''''''''''''''''''''''''
@ -659,13 +709,15 @@ This::
translates to::
if $kind & MATCH_SEQUENCE:
if $list is None:
$list = list($value)
if len($list) == 2:
a, b = $list
if a is b:
DONE
if $kind & MATCH_SEQUENCE == 0:
FAIL
if $list is None:
$list = list($value)
if len($list) != 2:
FAIL
a, b = $list
if not a is b:
FAIL
.. [3]
@ -675,12 +727,13 @@ This::
translates to::
if $kind & MATCH_SEQUENCE:
if $list is None:
$list = list($value)
if len($list) >= 2:
a, *b, c = $list
DONE
if $kind & MATCH_SEQUENCE == 0:
FAIL
if $list is None:
$list = list($value)
if len($list) < 2:
FAIL
a, *b, c = $list
.. [4]
@ -690,14 +743,16 @@ This::
translates to::
if $kind & MATCH_MAPPING:
if $dict is None:
$dict = dict($value)
if $dict.keys() == {"x", "y"}:
x = $dict["x"]
y = $dict["y"]
if x > 2:
DONE
if $kind & MATCH_MAPPING == 0:
FAIL
if $dict is None:
$dict = dict($value)
if $dict.keys() != {"x", "y"}:
FAIL
x = $dict["x"]
y = $dict["y"]
if not x > 2:
FAIL
.. [5]
@ -707,15 +762,16 @@ This::
translates to::
if $kind & MATCH_MAPPING:
if $dict is None:
$dict = dict($value)
if $dict.keys() >= {"x", "y"}:
$tmp = dict($dict)
x = $tmp.pop("x")
y = $tmp.pop("y")
z = $tmp
DONE
if $kind & MATCH_MAPPING == 0:
FAIL
if $dict is None:
$dict = dict($value)
if not $dict.keys() >= {"x", "y"}:
FAIL
$tmp = dict($dict)
x = $tmp.pop("x")
y = $tmp.pop("y")
z = $tmp
.. [6]
@ -725,18 +781,20 @@ This::
translates to::
if $kind & MATCH_CLASS:
if isinstance($value, ClsName):
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
$right_attrs = $attrs[1:]
if "a" in $right_attrs:
$y_index = $attrs.index("a")
x = $items[0]
y = $items[$y_index]
DONE
if $kind & MATCH_CLASS == 0:
FAIL
if not isinstance($value, ClsName):
FAIL
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
$right_attrs = $attrs[1:]
if not "a" in $right_attrs:
FAIL
$y_index = $attrs.index("a")
x = $items[0]
y = $items[$y_index]
.. [7]
@ -747,24 +805,34 @@ This::
translates to::
if $kind & MATCH_CLASS:
if isinstance($value, ClsName):
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
if "a" in $attrs and "b" in $attrs:
$x_index = $attrs.index("a")
x = $items[$x_index]
$y_index = $attrs.index("b")
y = $items[$y_index]
DONE
if not isinstance($value, ClsName):
FAIL
if $attrs is None:
$attrs = type($value).__attributes__
if $items is None:
$items = type($value).__deconstruct__($value)
if not "a" in $attrs:
FAIL
if not "b" in $attrs:
FAIL
$x_index = $attrs.index("a")
x = $items[$x_index]
$y_index = $attrs.index("b")
y = $items[$y_index]
elif $kind & MATCH_DEFAULT:
if isinstance($value, ClsName) and hasattr($value, "__dict__"):
$obj_dict = $value.__dict__
if "a" in $obj_dict and "b" in $obj_dict:
x = $obj_dict["a"]
y = $obj_dict["b"]
DONE
if not isinstance($value, ClsName):
FAIL
if not hasattr($value, "__dict__"):
FAIL
$obj_dict = $value.__dict__
if not "a" in $attrs:
FAIL
if not "b" in $attrs:
FAIL
x = $obj_dict["a"]
y = $obj_dict["b"]
else:
FAIL
Copyright