bpo-42128: Structural Pattern Matching (PEP 634) (GH-22917)

Co-authored-by: Guido van Rossum <guido@python.org>
Co-authored-by: Talin <viridia@gmail.com>
Co-authored-by: Pablo Galindo <pablogsal@gmail.com>
This commit is contained in:
Brandt Bucher
2021-02-26 14:51:55 -08:00
committed by GitHub
parent cc02b4f2e8
commit 145bf269df
43 changed files with 10867 additions and 2607 deletions

469
Python/Python-ast.c generated

File diff suppressed because it is too large Load Diff

View File

@@ -309,6 +309,14 @@ validate_expr(expr_ty exp, expr_context_ty ctx)
return validate_exprs(exp->v.Tuple.elts, ctx, 0);
case NamedExpr_kind:
return validate_expr(exp->v.NamedExpr.value, Load);
case MatchAs_kind:
PyErr_SetString(PyExc_ValueError,
"MatchAs is only valid in match_case patterns");
return 0;
case MatchOr_kind:
PyErr_SetString(PyExc_ValueError,
"MatchOr is only valid in match_case patterns");
return 0;
/* This last case doesn't have any checking. */
case Name_kind:
return 1;
@@ -317,6 +325,13 @@ validate_expr(expr_ty exp, expr_context_ty ctx)
return 0;
}
static int
validate_pattern(expr_ty p)
{
// Coming soon (thanks Batuhan)!
return 1;
}
static int
_validate_nonempty_seq(asdl_seq *seq, const char *what, const char *owner)
{
@@ -415,6 +430,20 @@ validate_stmt(stmt_ty stmt)
return 0;
}
return validate_body(stmt->v.AsyncWith.body, "AsyncWith");
case Match_kind:
if (!validate_expr(stmt->v.Match.subject, Load)
|| !validate_nonempty_seq(stmt->v.Match.cases, "cases", "Match")) {
return 0;
}
for (i = 0; i < asdl_seq_LEN(stmt->v.Match.cases); i++) {
match_case_ty m = asdl_seq_GET(stmt->v.Match.cases, i);
if (!validate_pattern(m->pattern)
|| (m->guard && !validate_expr(m->guard, Load))
|| !validate_body(m->body, "match_case")) {
return 0;
}
}
return 1;
case Raise_kind:
if (stmt->v.Raise.exc) {
return validate_expr(stmt->v.Raise.exc, Load) &&

View File

@@ -408,6 +408,9 @@ static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOp
static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_pattern(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
#define CALL(FUNC, TYPE, ARG) \
if (!FUNC((ARG), ctx_, state)) \
return 0;
@@ -590,6 +593,10 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
case Constant_kind:
// Already a constant, nothing further to do
break;
case MatchAs_kind:
case MatchOr_kind:
// These can't occur outside of patterns.
Py_UNREACHABLE();
// No default case, so the compiler will emit a warning if new expression
// kinds are added without being handled here
}
@@ -709,6 +716,10 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
case Expr_kind:
CALL(astfold_expr, expr_ty, node_->v.Expr.value);
break;
case Match_kind:
CALL(astfold_expr, expr_ty, node_->v.Match.subject);
CALL_SEQ(astfold_match_case, match_case, node_->v.Match.cases);
break;
// The following statements don't contain any subexpressions to be folded
case Import_kind:
case ImportFrom_kind:
@@ -746,6 +757,125 @@ astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
return 1;
}
static int
astfold_pattern_negative(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
assert(node_->kind == UnaryOp_kind);
assert(node_->v.UnaryOp.op == USub);
assert(node_->v.UnaryOp.operand->kind == Constant_kind);
PyObject *value = node_->v.UnaryOp.operand->v.Constant.value;
assert(PyComplex_CheckExact(value) ||
PyFloat_CheckExact(value) ||
PyLong_CheckExact(value));
PyObject *negated = PyNumber_Negative(value);
if (negated == NULL) {
return 0;
}
assert(PyComplex_CheckExact(negated) ||
PyFloat_CheckExact(negated) ||
PyLong_CheckExact(negated));
return make_const(node_, negated, ctx_);
}
static int
astfold_pattern_complex(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
expr_ty left = node_->v.BinOp.left;
expr_ty right = node_->v.BinOp.right;
if (left->kind == UnaryOp_kind) {
CALL(astfold_pattern_negative, expr_ty, left);
}
assert(left->kind = Constant_kind);
assert(right->kind = Constant_kind);
// LHS must be real, RHS must be imaginary:
if (!(PyFloat_CheckExact(left->v.Constant.value) ||
PyLong_CheckExact(left->v.Constant.value)) ||
!PyComplex_CheckExact(right->v.Constant.value))
{
// Not actually valid, but it's the compiler's job to complain:
return 1;
}
PyObject *new;
if (node_->v.BinOp.op == Add) {
new = PyNumber_Add(left->v.Constant.value, right->v.Constant.value);
}
else {
assert(node_->v.BinOp.op == Sub);
new = PyNumber_Subtract(left->v.Constant.value, right->v.Constant.value);
}
if (new == NULL) {
return 0;
}
assert(PyComplex_CheckExact(new));
return make_const(node_, new, ctx_);
}
static int
astfold_pattern_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
CALL(astfold_pattern, expr_ty, node_->value);
return 1;
}
static int
astfold_pattern(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
// Don't blindly optimize the pattern as an expr; it plays by its own rules!
// Currently, this is only used to form complex/negative numeric constants.
switch (node_->kind) {
case Attribute_kind:
break;
case BinOp_kind:
CALL(astfold_pattern_complex, expr_ty, node_);
break;
case Call_kind:
CALL_SEQ(astfold_pattern, expr, node_->v.Call.args);
CALL_SEQ(astfold_pattern_keyword, keyword, node_->v.Call.keywords);
break;
case Constant_kind:
break;
case Dict_kind:
CALL_SEQ(astfold_pattern, expr, node_->v.Dict.keys);
CALL_SEQ(astfold_pattern, expr, node_->v.Dict.values);
break;
// Not actually valid, but it's the compiler's job to complain:
case JoinedStr_kind:
break;
case List_kind:
CALL_SEQ(astfold_pattern, expr, node_->v.List.elts);
break;
case MatchAs_kind:
CALL(astfold_pattern, expr_ty, node_->v.MatchAs.pattern);
break;
case MatchOr_kind:
CALL_SEQ(astfold_pattern, expr, node_->v.MatchOr.patterns);
break;
case Name_kind:
break;
case Starred_kind:
CALL(astfold_pattern, expr_ty, node_->v.Starred.value);
break;
case Tuple_kind:
CALL_SEQ(astfold_pattern, expr, node_->v.Tuple.elts);
break;
case UnaryOp_kind:
CALL(astfold_pattern_negative, expr_ty, node_);
break;
default:
Py_UNREACHABLE();
}
return 1;
}
static int
astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
CALL(astfold_pattern, expr_ty, node_->pattern);
CALL_OPT(astfold_expr, expr_ty, node_->guard);
CALL_SEQ(astfold_stmt, stmt, node_->body);
return 1;
}
#undef CALL
#undef CALL_OPT
#undef CALL_SEQ

View File

@@ -880,6 +880,230 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
return 0;
}
// PEP 634: Structural Pattern Matching
// Return a tuple of values corresponding to keys, with error checks for
// duplicate/missing keys.
static PyObject*
match_keys(PyThreadState *tstate, PyObject *map, PyObject *keys)
{
assert(PyTuple_CheckExact(keys));
Py_ssize_t nkeys = PyTuple_GET_SIZE(keys);
if (!nkeys) {
// No keys means no items.
return PyTuple_New(0);
}
PyObject *seen = NULL;
PyObject *dummy = NULL;
PyObject *values = NULL;
// We use the two argument form of map.get(key, default) for two reasons:
// - Atomically check for a key and get its value without error handling.
// - Don't cause key creation or resizing in dict subclasses like
// collections.defaultdict that define __missing__ (or similar).
_Py_IDENTIFIER(get);
PyObject *get = _PyObject_GetAttrId(map, &PyId_get);
if (get == NULL) {
goto fail;
}
seen = PySet_New(NULL);
if (seen == NULL) {
goto fail;
}
// dummy = object()
dummy = _PyObject_CallNoArg((PyObject *)&PyBaseObject_Type);
if (dummy == NULL) {
goto fail;
}
values = PyList_New(0);
if (values == NULL) {
goto fail;
}
for (Py_ssize_t i = 0; i < nkeys; i++) {
PyObject *key = PyTuple_GET_ITEM(keys, i);
if (PySet_Contains(seen, key) || PySet_Add(seen, key)) {
if (!_PyErr_Occurred(tstate)) {
// Seen it before!
_PyErr_Format(tstate, PyExc_ValueError,
"mapping pattern checks duplicate key (%R)", key);
}
goto fail;
}
PyObject *value = PyObject_CallFunctionObjArgs(get, key, dummy, NULL);
if (value == NULL) {
goto fail;
}
if (value == dummy) {
// key not in map!
Py_DECREF(value);
Py_DECREF(values);
// Return None:
Py_INCREF(Py_None);
values = Py_None;
goto done;
}
PyList_Append(values, value);
Py_DECREF(value);
}
Py_SETREF(values, PyList_AsTuple(values));
// Success:
done:
Py_DECREF(get);
Py_DECREF(seen);
Py_DECREF(dummy);
return values;
fail:
Py_XDECREF(get);
Py_XDECREF(seen);
Py_XDECREF(dummy);
Py_XDECREF(values);
return NULL;
}
// Extract a named attribute from the subject, with additional bookkeeping to
// raise TypeErrors for repeated lookups. On failure, return NULL (with no
// error set). Use _PyErr_Occurred(tstate) to disambiguate.
static PyObject*
match_class_attr(PyThreadState *tstate, PyObject *subject, PyObject *type,
PyObject *name, PyObject *seen)
{
assert(PyUnicode_CheckExact(name));
assert(PySet_CheckExact(seen));
if (PySet_Contains(seen, name) || PySet_Add(seen, name)) {
if (!_PyErr_Occurred(tstate)) {
// Seen it before!
_PyErr_Format(tstate, PyExc_TypeError,
"%s() got multiple sub-patterns for attribute %R",
((PyTypeObject*)type)->tp_name, name);
}
return NULL;
}
PyObject *attr = PyObject_GetAttr(subject, name);
if (attr == NULL && _PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
_PyErr_Clear(tstate);
}
return attr;
}
// On success (match), return a tuple of extracted attributes. On failure (no
// match), return NULL. Use _PyErr_Occurred(tstate) to disambiguate.
static PyObject*
match_class(PyThreadState *tstate, PyObject *subject, PyObject *type,
Py_ssize_t nargs, PyObject *kwargs)
{
if (!PyType_Check(type)) {
const char *e = "called match pattern must be a type";
_PyErr_Format(tstate, PyExc_TypeError, e);
return NULL;
}
assert(PyTuple_CheckExact(kwargs));
// First, an isinstance check:
if (PyObject_IsInstance(subject, type) <= 0) {
return NULL;
}
// So far so good:
PyObject *seen = PySet_New(NULL);
if (seen == NULL) {
return NULL;
}
PyObject *attrs = PyList_New(0);
if (attrs == NULL) {
Py_DECREF(seen);
return NULL;
}
// NOTE: From this point on, goto fail on failure:
PyObject *match_args = NULL;
// First, the positional subpatterns:
if (nargs) {
int match_self = 0;
match_args = PyObject_GetAttrString(type, "__match_args__");
if (match_args) {
if (PyList_CheckExact(match_args)) {
Py_SETREF(match_args, PyList_AsTuple(match_args));
}
if (match_args == NULL) {
goto fail;
}
if (!PyTuple_CheckExact(match_args)) {
const char *e = "%s.__match_args__ must be a list or tuple "
"(got %s)";
_PyErr_Format(tstate, PyExc_TypeError, e,
((PyTypeObject *)type)->tp_name,
Py_TYPE(match_args)->tp_name);
goto fail;
}
}
else if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
_PyErr_Clear(tstate);
// _Py_TPFLAGS_MATCH_SELF is only acknowledged if the type does not
// define __match_args__. This is natural behavior for subclasses:
// it's as if __match_args__ is some "magic" value that is lost as
// soon as they redefine it.
match_args = PyTuple_New(0);
match_self = PyType_HasFeature((PyTypeObject*)type,
_Py_TPFLAGS_MATCH_SELF);
}
else {
goto fail;
}
assert(PyTuple_CheckExact(match_args));
Py_ssize_t allowed = match_self ? 1 : PyTuple_GET_SIZE(match_args);
if (allowed < nargs) {
const char *plural = (allowed == 1) ? "" : "s";
_PyErr_Format(tstate, PyExc_TypeError,
"%s() accepts %d positional sub-pattern%s (%d given)",
((PyTypeObject*)type)->tp_name,
allowed, plural, nargs);
goto fail;
}
if (match_self) {
// Easy. Copy the subject itself, and move on to kwargs.
PyList_Append(attrs, subject);
}
else {
for (Py_ssize_t i = 0; i < nargs; i++) {
PyObject *name = PyTuple_GET_ITEM(match_args, i);
if (!PyUnicode_CheckExact(name)) {
_PyErr_Format(tstate, PyExc_TypeError,
"__match_args__ elements must be strings "
"(got %s)", Py_TYPE(name)->tp_name);
goto fail;
}
PyObject *attr = match_class_attr(tstate, subject, type, name,
seen);
if (attr == NULL) {
goto fail;
}
PyList_Append(attrs, attr);
Py_DECREF(attr);
}
}
Py_CLEAR(match_args);
}
// Finally, the keyword subpatterns:
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwargs); i++) {
PyObject *name = PyTuple_GET_ITEM(kwargs, i);
PyObject *attr = match_class_attr(tstate, subject, type, name, seen);
if (attr == NULL) {
goto fail;
}
PyList_Append(attrs, attr);
Py_DECREF(attr);
}
Py_SETREF(attrs, PyList_AsTuple(attrs));
Py_DECREF(seen);
return attrs;
fail:
// We really don't care whether an error was raised or not... that's our
// caller's problem. All we know is that the match failed.
Py_XDECREF(match_args);
Py_DECREF(seen);
Py_DECREF(attrs);
return NULL;
}
static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause);
static int unpack_iterable(PyThreadState *, PyObject *, int, int, PyObject **);
@@ -3618,6 +3842,164 @@ main_loop:
#endif
}
case TARGET(GET_LEN): {
// PUSH(len(TOS))
Py_ssize_t len_i = PyObject_Length(TOP());
if (len_i < 0) {
goto error;
}
PyObject *len_o = PyLong_FromSsize_t(len_i);
if (len_o == NULL) {
goto error;
}
PUSH(len_o);
DISPATCH();
}
case TARGET(MATCH_CLASS): {
// Pop TOS. On success, set TOS to True and TOS1 to a tuple of
// attributes. On failure, set TOS to False.
PyObject *names = POP();
PyObject *type = TOP();
PyObject *subject = SECOND();
assert(PyTuple_CheckExact(names));
PyObject *attrs = match_class(tstate, subject, type, oparg, names);
Py_DECREF(names);
if (attrs) {
// Success!
assert(PyTuple_CheckExact(attrs));
Py_DECREF(subject);
SET_SECOND(attrs);
}
else if (_PyErr_Occurred(tstate)) {
goto error;
}
Py_DECREF(type);
SET_TOP(PyBool_FromLong(!!attrs));
DISPATCH();
}
case TARGET(MATCH_MAPPING): {
// PUSH(isinstance(TOS, _collections_abc.Mapping))
PyObject *subject = TOP();
// Fast path for dicts:
if (PyDict_Check(subject)) {
Py_INCREF(Py_True);
PUSH(Py_True);
DISPATCH();
}
// Lazily import _collections_abc.Mapping, and keep it handy on the
// PyInterpreterState struct (it gets cleaned up at exit):
PyInterpreterState *interp = PyInterpreterState_Get();
if (interp->map_abc == NULL) {
PyObject *abc = PyImport_ImportModule("_collections_abc");
if (abc == NULL) {
goto error;
}
interp->map_abc = PyObject_GetAttrString(abc, "Mapping");
if (interp->map_abc == NULL) {
goto error;
}
}
int match = PyObject_IsInstance(subject, interp->map_abc);
if (match < 0) {
goto error;
}
PUSH(PyBool_FromLong(match));
DISPATCH();
}
case TARGET(MATCH_SEQUENCE): {
// PUSH(not isinstance(TOS, (bytearray, bytes, str))
// and isinstance(TOS, _collections_abc.Sequence))
PyObject *subject = TOP();
// Fast path for lists and tuples:
if (PyType_FastSubclass(Py_TYPE(subject),
Py_TPFLAGS_LIST_SUBCLASS |
Py_TPFLAGS_TUPLE_SUBCLASS))
{
Py_INCREF(Py_True);
PUSH(Py_True);
DISPATCH();
}
// Bail on some possible Sequences that we intentionally exclude:
if (PyType_FastSubclass(Py_TYPE(subject),
Py_TPFLAGS_BYTES_SUBCLASS |
Py_TPFLAGS_UNICODE_SUBCLASS) ||
PyByteArray_Check(subject))
{
Py_INCREF(Py_False);
PUSH(Py_False);
DISPATCH();
}
// Lazily import _collections_abc.Sequence, and keep it handy on the
// PyInterpreterState struct (it gets cleaned up at exit):
PyInterpreterState *interp = PyInterpreterState_Get();
if (interp->seq_abc == NULL) {
PyObject *abc = PyImport_ImportModule("_collections_abc");
if (abc == NULL) {
goto error;
}
interp->seq_abc = PyObject_GetAttrString(abc, "Sequence");
if (interp->seq_abc == NULL) {
goto error;
}
}
int match = PyObject_IsInstance(subject, interp->seq_abc);
if (match < 0) {
goto error;
}
PUSH(PyBool_FromLong(match));
DISPATCH();
}
case TARGET(MATCH_KEYS): {
// On successful match for all keys, PUSH(values) and PUSH(True).
// Otherwise, PUSH(None) and PUSH(False).
PyObject *keys = TOP();
PyObject *subject = SECOND();
PyObject *values_or_none = match_keys(tstate, subject, keys);
if (values_or_none == NULL) {
goto error;
}
PUSH(values_or_none);
if (values_or_none == Py_None) {
Py_INCREF(Py_False);
PUSH(Py_False);
DISPATCH();
}
assert(PyTuple_CheckExact(values_or_none));
Py_INCREF(Py_True);
PUSH(Py_True);
DISPATCH();
}
case TARGET(COPY_DICT_WITHOUT_KEYS): {
// rest = dict(TOS1)
// for key in TOS:
// del rest[key]
// SET_TOP(rest)
PyObject *keys = TOP();
PyObject *subject = SECOND();
PyObject *rest = PyDict_New();
if (rest == NULL || PyDict_Update(rest, subject)) {
Py_XDECREF(rest);
goto error;
}
// This may seem a bit inefficient, but keys is rarely big enough to
// actually impact runtime.
assert(PyTuple_CheckExact(keys));
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(keys); i++) {
if (PyDict_DelItem(rest, PyTuple_GET_ITEM(keys, i))) {
Py_DECREF(rest);
goto error;
}
}
Py_DECREF(keys);
SET_TOP(rest);
DISPATCH();
}
case TARGET(GET_ITER): {
/* before: [obj]; after [getiter(obj)] */
PyObject *iterable = TOP();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -29,11 +29,11 @@ static void *opcode_targets[256] = {
&&TARGET_BINARY_TRUE_DIVIDE,
&&TARGET_INPLACE_FLOOR_DIVIDE,
&&TARGET_INPLACE_TRUE_DIVIDE,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&TARGET_GET_LEN,
&&TARGET_MATCH_MAPPING,
&&TARGET_MATCH_SEQUENCE,
&&TARGET_MATCH_KEYS,
&&TARGET_COPY_DICT_WITHOUT_KEYS,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
@@ -151,7 +151,7 @@ static void *opcode_targets[256] = {
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&TARGET_MATCH_CLASS,
&&_unknown_opcode,
&&TARGET_SETUP_ASYNC_WITH,
&&TARGET_FORMAT_VALUE,

View File

@@ -308,6 +308,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->importlib);
Py_CLEAR(interp->import_func);
Py_CLEAR(interp->dict);
Py_CLEAR(interp->map_abc);
Py_CLEAR(interp->seq_abc);
#ifdef HAVE_FORK
Py_CLEAR(interp->before_forkers);
Py_CLEAR(interp->after_forkers_parent);

View File

@@ -207,6 +207,7 @@ static int symtable_visit_argannotations(struct symtable *st, asdl_arg_seq *args
static int symtable_implicit_arg(struct symtable *st, int pos);
static int symtable_visit_annotations(struct symtable *st, arguments_ty, expr_ty);
static int symtable_visit_withitem(struct symtable *st, withitem_ty item);
static int symtable_visit_match_case(struct symtable *st, match_case_ty m);
static identifier top = NULL, lambda = NULL, genexpr = NULL,
@@ -239,6 +240,7 @@ symtable_new(void)
goto fail;
st->st_cur = NULL;
st->st_private = NULL;
st->in_pattern = 0;
return st;
fail:
PySymtable_Free(st);
@@ -1294,6 +1296,10 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
if (s->v.If.orelse)
VISIT_SEQ(st, stmt, s->v.If.orelse);
break;
case Match_kind:
VISIT(st, expr, s->v.Match.subject);
VISIT_SEQ(st, match_case, s->v.Match.cases);
break;
case Raise_kind:
if (s->v.Raise.exc) {
VISIT(st, expr, s->v.Raise.exc);
@@ -1648,6 +1654,13 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
VISIT(st, expr, e->v.Slice.step)
break;
case Name_kind:
// Don't make "_" a local when used in a pattern:
if (st->in_pattern &&
e->v.Name.ctx == Store &&
_PyUnicode_EqualToASCIIString(e->v.Name.id, "_"))
{
break;
}
if (!symtable_add_def(st, e->v.Name.id,
e->v.Name.ctx == Load ? USE : DEF_LOCAL))
VISIT_QUIT(st, 0);
@@ -1667,6 +1680,13 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
case Tuple_kind:
VISIT_SEQ(st, expr, e->v.Tuple.elts);
break;
case MatchAs_kind:
VISIT(st, expr, e->v.MatchAs.pattern);
symtable_add_def(st, e->v.MatchAs.name, DEF_LOCAL);
break;
case MatchOr_kind:
VISIT_SEQ(st, expr, e->v.MatchOr.patterns);
break;
}
VISIT_QUIT(st, 1);
}
@@ -1785,6 +1805,20 @@ symtable_visit_withitem(struct symtable *st, withitem_ty item)
return 1;
}
static int
symtable_visit_match_case(struct symtable *st, match_case_ty m)
{
assert(!st->in_pattern);
st->in_pattern = 1;
VISIT(st, expr, m->pattern);
assert(st->in_pattern);
st->in_pattern = 0;
if (m->guard) {
VISIT(st, expr, m->guard);
}
VISIT_SEQ(st, stmt, m->body);
return 1;
}
static int
symtable_visit_alias(struct symtable *st, alias_ty a)