diff --git a/toolchain/check/handle_where.cpp b/toolchain/check/handle_where.cpp index a5dc86ad640b..b41f4f0eebca 100644 --- a/toolchain/check/handle_where.cpp +++ b/toolchain/check/handle_where.cpp @@ -36,6 +36,11 @@ auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id) return context.TODO(node_id, "HandleRequirementImpls"); } +auto HandleParseNode(Context& context, Parse::RequirementAndId node_id) + -> bool { + return context.TODO(node_id, "HandleRequirementAnd"); +} + auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool { return context.TODO(node_id, "HandleWhereExpr"); } diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index d00214ffdbbf..fd1db507e030 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -619,6 +619,7 @@ class NodeStack { case Parse::NodeKind::PrivateModifier: case Parse::NodeKind::ProtectedModifier: case Parse::NodeKind::RealLiteral: + case Parse::NodeKind::RequirementAnd: case Parse::NodeKind::RequirementEqual: case Parse::NodeKind::RequirementEqualEqual: case Parse::NodeKind::RequirementImpls: diff --git a/toolchain/lex/token_kind.h b/toolchain/lex/token_kind.h index bde12c7bb68c..abe69dfd0c4f 100644 --- a/toolchain/lex/token_kind.h +++ b/toolchain/lex/token_kind.h @@ -94,7 +94,7 @@ class TokenKind : public CARBON_ENUM_BASE(TokenKind) { // Get the expected number of parse tree nodes that will be created for this // token. - auto expected_parse_tree_size() const -> int { + auto expected_max_parse_tree_size() const -> int { return ExpectedParseTreeSize[AsInt()]; } diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index 8fd58dc52406..66c3d8ad3f33 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -365,7 +365,7 @@ auto TokenizedBuffer::GetTokenInfo(TokenIndex token) const -> const TokenInfo& { auto TokenizedBuffer::AddToken(TokenInfo info) -> TokenIndex { token_infos_.push_back(info); - expected_parse_tree_size_ += info.kind().expected_parse_tree_size(); + expected_max_parse_tree_size_ += info.kind().expected_max_parse_tree_size(); return TokenIndex(static_cast(token_infos_.size()) - 1); } diff --git a/toolchain/lex/tokenized_buffer.h b/toolchain/lex/tokenized_buffer.h index 8e0c38a74be6..ebf12855d772 100644 --- a/toolchain/lex/tokenized_buffer.h +++ b/toolchain/lex/tokenized_buffer.h @@ -219,8 +219,10 @@ class TokenizedBuffer : public Printable { auto size() const -> int { return token_infos_.size(); } - auto expected_parse_tree_size() const -> int { - return expected_parse_tree_size_; + // This is an upper bound on the number of output parse nodes in the absence + // of errors. + auto expected_max_parse_tree_size() const -> int { + return expected_max_parse_tree_size_; } auto source() const -> const SourceBuffer& { return *source_; } @@ -456,9 +458,9 @@ class TokenizedBuffer : public Printable { llvm::SmallVector line_infos_; - // The number of parse tree nodes that we expect to be created for the tokens - // in this buffer. - int expected_parse_tree_size_ = 0; + // An upper bound on the number of parse tree nodes that we expect to be + // created for the tokens in this buffer. + int expected_max_parse_tree_size_ = 0; bool has_errors_ = false; diff --git a/toolchain/parse/handle_requirement.cpp b/toolchain/parse/handle_requirement.cpp index 56428d1f3b6d..cdebda1c6f0c 100644 --- a/toolchain/parse/handle_requirement.cpp +++ b/toolchain/parse/handle_requirement.cpp @@ -10,6 +10,7 @@ namespace Carbon::Parse { auto HandleRequirementBegin(Context& context) -> void { context.PopAndDiscardState(); + // TODO: Peek ahead for `.designator = ...`, and give it special handling. context.PushState(State::RequirementOperator); context.PushStateForExpr(PrecedenceGroup::ForRequirements()); } @@ -69,7 +70,13 @@ auto HandleRequirementOperatorFinish(Context& context) -> void { << token_kind; return; } - // TODO: Handle `and` token. + if (state.has_error) { + context.ReturnErrorOnState(); + } + if (auto token = context.ConsumeIf(Lex::TokenKind::And)) { + context.AddNode(NodeKind::RequirementAnd, *token, /*has_error=*/false); + context.PushState(State::RequirementBegin); + } } auto HandleWhereFinish(Context& context) -> void { diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index aa8c25586a1b..1a6c9bb6ee6b 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -274,8 +274,9 @@ CARBON_PARSE_NODE_KIND(ShortCircuitOperandOr) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorAnd) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorOr) -CARBON_PARSE_NODE_KIND(SelfTypeName) CARBON_PARSE_NODE_KIND(DesignatorExpr) +CARBON_PARSE_NODE_KIND(SelfTypeName) +CARBON_PARSE_NODE_KIND(RequirementAnd) CARBON_PARSE_NODE_KIND(RequirementEqual) CARBON_PARSE_NODE_KIND(RequirementEqualEqual) CARBON_PARSE_NODE_KIND(RequirementImpls) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 40c65ef01014..a956ed4559aa 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -645,6 +645,13 @@ CARBON_PARSE_STATE(RequirementOperator) // expr where expr == expr // ^ // (state done) +// expr where expr impls expr and +// ^~~ +// expr where expr = expr and +// ^~~ +// expr where expr == expr and +// ^~~ +// 1. RequirementBegin CARBON_PARSE_STATE(RequirementOperatorFinish) // Finishes an `where` expression. diff --git a/toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon b/toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon deleted file mode 100644 index f67ac6a98a5b..000000000000 --- a/toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon +++ /dev/null @@ -1,36 +0,0 @@ -// Part of the Carbon Language project, under the Apache License v2.0 with LLVM -// Exceptions. See /LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// AUTOUPDATE -// TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon -// TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon - -// TODO: Support `and` to allow multiple requirements in `where` expressions. - -// CHECK:STDERR: fail_todo_where_and.carbon:[[@LINE+3]]:31: ERROR: Expected `,` or `)`. -// CHECK:STDERR: fn Foo(T: type where .U = i32 and .V == .W and .X impls I); -// CHECK:STDERR: ^~~ -fn Foo(T: type where .U = i32 and .V == .W and .X impls I); - -// CHECK:STDOUT: - filename: fail_todo_where_and.carbon -// CHECK:STDOUT: parse_tree: [ -// CHECK:STDOUT: {kind: 'FileStart', text: ''}, -// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Foo'}, -// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, -// CHECK:STDOUT: {kind: 'IdentifierName', text: 'T'}, -// CHECK:STDOUT: {kind: 'TypeTypeLiteral', text: 'type'}, -// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'IdentifierName', text: 'U'}, -// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, -// CHECK:STDOUT: {kind: 'RequirementEqual', text: '=', subtree_size: 4}, -// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', subtree_size: 7}, -// CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 9}, -// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', has_error: yes, subtree_size: 11}, -// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 14}, -// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, -// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/where_expr/where_and.carbon b/toolchain/parse/testdata/where_expr/where_and.carbon new file mode 100644 index 000000000000..b7e850d5dfe8 --- /dev/null +++ b/toolchain/parse/testdata/where_expr/where_and.carbon @@ -0,0 +1,143 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/where_expr/where_and.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/where_expr/where_and.carbon + +// --- success.carbon + +fn OneAnd(T:! type where .U = i32 and .V == .W); + +fn TwoAnd(Y:! I where .J impls K and .L == .M and .N = bool); + +// --- fail_and_prefix.carbon + +// CHECK:STDERR: fail_and_prefix.carbon:[[@LINE+4]]:29: ERROR: Expected expression. +// CHECK:STDERR: fn AndPrefix(T:! type where and .V == .W); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +fn AndPrefix(T:! type where and .V == .W); + +// --- fail_and_suffix.carbon + +// CHECK:STDERR: fail_and_suffix.carbon:[[@LINE+4]]:40: ERROR: Expected expression. +// CHECK:STDERR: fn AndSuffix(Y:! I where .J impls K and); +// CHECK:STDERR: ^ +// CHECK:STDERR: +fn AndSuffix(Y:! I where .J impls K and); + +// --- fail_and_early.carbon + +// CHECK:STDERR: fail_and_early.carbon:[[@LINE+3]]:28: ERROR: Requirement should use `impls`, `=`, or `==` operator. +// CHECK:STDERR: fn AndEarly(Z:! L where .M and N); +// CHECK:STDERR: ^~~ +fn AndEarly(Z:! L where .M and N); + +// CHECK:STDOUT: - filename: success.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'OneAnd'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'T'}, +// CHECK:STDOUT: {kind: 'TypeTypeLiteral', text: 'type'}, +// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'U'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'RequirementEqual', text: '=', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'RequirementAnd', text: 'and'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'V'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'W'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'RequirementEqualEqual', text: '==', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', subtree_size: 13}, +// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 17}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 20}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'TwoAnd'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Y'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'I'}, +// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'J'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'K'}, +// CHECK:STDOUT: {kind: 'RequirementImpls', text: 'impls', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'RequirementAnd', text: 'and'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'L'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'M'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'RequirementEqualEqual', text: '==', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'RequirementAnd', text: 'and'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'N'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'BoolTypeLiteral', text: 'bool'}, +// CHECK:STDOUT: {kind: 'RequirementEqual', text: '=', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 20}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 22}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 25}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_and_prefix.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'AndPrefix'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'T'}, +// CHECK:STDOUT: {kind: 'TypeTypeLiteral', text: 'type'}, +// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'and', has_error: yes}, +// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', has_error: yes, subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_and_suffix.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'AndSuffix'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Y'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'I'}, +// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'J'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'K'}, +// CHECK:STDOUT: {kind: 'RequirementImpls', text: 'impls', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'RequirementAnd', text: 'and'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ')', has_error: yes}, +// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 9}, +// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', has_error: yes, subtree_size: 11}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', has_error: yes, subtree_size: 13}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 16}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_and_early.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'AndEarly'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Z'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'L'}, +// CHECK:STDOUT: {kind: 'WhereOperand', text: 'where', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'M'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', has_error: yes, subtree_size: 7}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', has_error: yes, subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/tree.cpp b/toolchain/parse/tree.cpp index 2e3a907edb4f..a6f27d719ccf 100644 --- a/toolchain/parse/tree.cpp +++ b/toolchain/parse/tree.cpp @@ -45,12 +45,20 @@ auto Tree::Verify() const -> ErrorOr { } } - if (!has_errors() && - static_cast(size()) != tokens_->expected_parse_tree_size()) { + // Not every token that can produce a virtual node will, so we only check that + // the number of nodes is in a range. + int32_t num_nodes = size(); + if (!has_errors() && num_nodes > tokens_->expected_max_parse_tree_size()) { return Error(llvm::formatv( "Tree has {0} nodes and no errors, but " - "Lex::TokenizedBuffer expected {1} nodes for {2} tokens.", - size(), tokens_->expected_parse_tree_size(), tokens_->size())); + "Lex::TokenizedBuffer expected up to {1} nodes for {2} tokens.", + num_nodes, tokens_->expected_max_parse_tree_size(), tokens_->size())); + } + if (!has_errors() && num_nodes < tokens_->size()) { + return Error( + llvm::formatv("Tree has {0} nodes and no errors, but expected at least " + "{1} nodes to match the number of tokens.", + num_nodes, tokens_->size())); } #ifndef NDEBUG diff --git a/toolchain/parse/tree.h b/toolchain/parse/tree.h index da31781423c1..9f1189caa6bf 100644 --- a/toolchain/parse/tree.h +++ b/toolchain/parse/tree.h @@ -100,7 +100,7 @@ class Tree : public Printable { // be used to actually parse the tokens into a tree. explicit Tree(Lex::TokenizedBuffer& tokens_arg) : tokens_(&tokens_arg) { // If the tree is valid, there will be one node per token, so reserve once. - node_impls_.reserve(tokens_->expected_parse_tree_size()); + node_impls_.reserve(tokens_->expected_max_parse_tree_size()); } auto has_errors() const -> bool { return has_errors_; } diff --git a/toolchain/parse/tree_and_subtrees.cpp b/toolchain/parse/tree_and_subtrees.cpp index a4792c935c5b..4f1d0a47150a 100644 --- a/toolchain/parse/tree_and_subtrees.cpp +++ b/toolchain/parse/tree_and_subtrees.cpp @@ -29,8 +29,9 @@ TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens, size += subtree_sizes_[child.index]; if (kind.has_bracket() && i == kind.child_count() - 1) { CARBON_CHECK(kind.bracket() == tree.node_kind(child)) - << "Node " << kind << " needs bracket " << kind.bracket() - << ", found wrong bracket " << tree.node_kind(child); + << "Node " << kind << " with child count " << kind.child_count() + << " needs bracket " << kind.bracket() << ", found wrong bracket " + << tree.node_kind(child); } } } else { diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 3ca9d931e1d8..01a82f4cc535 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1059,6 +1059,9 @@ struct RequirementImpls { AnyExprId rhs; }; +// An `and` token separating requirements in a `where` expression. +using RequirementAnd = LeafNode; + struct WhereOperand { static constexpr auto Kind = NodeKind::WhereOperand.Define({.child_count = 1}); @@ -1069,13 +1072,11 @@ struct WhereOperand { }; struct WhereExpr { - static constexpr auto Kind = - NodeKind::WhereExpr.Define({.category = NodeCategory::Expr, - .bracketed_by = WhereOperand::Kind, - .child_count = 2}); + static constexpr auto Kind = NodeKind::WhereExpr.Define( + {.category = NodeCategory::Expr, .bracketed_by = WhereOperand::Kind}); WhereOperandId introducer; Lex::WhereTokenIndex token; - AnyRequirementId requirements; + CommaSeparatedList requirements; }; // Choice nodes