Просмотр исходного кода

Parse support for `where` operator (#4275)

Includes support for the `impls`, `=`, and `==` requirement operators to
the right of a `where`, but `and` to allow multiple requirements is
still a TODO.

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b 1 год назад
Родитель
Сommit
c33c9a02f6

+ 23 - 0
toolchain/check/handle_where.cpp

@@ -17,4 +17,27 @@ auto HandleParseNode(Context& context, Parse::DesignatorExprId node_id)
   return context.TODO(node_id, "HandleDesignatorExpr");
 }
 
+auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
+  return context.TODO(node_id, "HandleWhereOperand");
+}
+
+auto HandleParseNode(Context& context, Parse::RequirementEqualId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleRequirementEqual");
+}
+
+auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleRequirementEqualEqual");
+}
+
+auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleRequirementImpls");
+}
+
+auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
+  return context.TODO(node_id, "HandleWhereExpr");
+}
+
 }  // namespace Carbon::Check

+ 5 - 0
toolchain/check/node_stack.h

@@ -621,6 +621,9 @@ class NodeStack {
         case Parse::NodeKind::PrivateModifier:
         case Parse::NodeKind::ProtectedModifier:
         case Parse::NodeKind::RealLiteral:
+        case Parse::NodeKind::RequirementEqual:
+        case Parse::NodeKind::RequirementEqualEqual:
+        case Parse::NodeKind::RequirementImpls:
         case Parse::NodeKind::ReturnStatement:
         case Parse::NodeKind::SelfTypeName:
         case Parse::NodeKind::SelfTypeNameExpr:
@@ -641,6 +644,8 @@ class NodeStack {
         case Parse::NodeKind::UnsignedIntTypeLiteral:
         case Parse::NodeKind::VariableDecl:
         case Parse::NodeKind::VirtualModifier:
+        case Parse::NodeKind::WhereExpr:
+        case Parse::NodeKind::WhereOperand:
         case Parse::NodeKind::WhileStatement:
           return Id::Kind::Invalid;
       }

+ 3 - 33
toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon

@@ -10,9 +10,9 @@
 
 interface I { let T:! type; }
 
-// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:16: ERROR: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition.
+// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `HandleWhereOperand`.
 // CHECK:STDERR: impl bool as I where .T = bool {}
-// CHECK:STDERR:                ^~~~~
+// CHECK:STDERR:              ^~~~~~~
 impl bool as I where .T = bool {}
 
 // CHECK:STDOUT: --- fail_todo_impl_assoc_const.carbon
@@ -27,35 +27,7 @@ impl bool as I where .T = bool {}
 // CHECK:STDOUT:   %Bool: %Bool.type = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
-// CHECK:STDOUT:     .Bool = %import_ref
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/operators
-// CHECK:STDOUT:     import Core//prelude/types
-// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
-// CHECK:STDOUT:     import Core//prelude/operators/as
-// CHECK:STDOUT:     import Core//prelude/operators/bitwise
-// CHECK:STDOUT:     import Core//prelude/operators/comparison
-// CHECK:STDOUT:     import Core//prelude/types/bool
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %import_ref: %Bool.type = import_ref Core//prelude/types/bool, inst+2, loaded [template = constants.%Bool]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .I = %I.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {}
-// CHECK:STDOUT:   impl_decl @impl {
-// CHECK:STDOUT:     %bool.make_type: init type = call constants.%Bool() [template = bool]
-// CHECK:STDOUT:     %.loc16_6.1: type = value_of_initializer %bool.make_type [template = bool]
-// CHECK:STDOUT:     %.loc16_6.2: type = converted %bool.make_type, %.loc16_6.1 [template = bool]
-// CHECK:STDOUT:     %I.ref: type = name_ref I, %I.decl [template = constants.%.1]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
+// CHECK:STDOUT: file {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
@@ -68,7 +40,5 @@ impl bool as I where .T = bool {}
 // CHECK:STDOUT:   witness = (%T)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl: bool as %.1;
-// CHECK:STDOUT:
 // CHECK:STDOUT: fn @Bool() -> type = "bool.make_type";
 // CHECK:STDOUT:

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -127,6 +127,9 @@ CARBON_DIAGNOSTIC_KIND(ImplExpectedAs)
 // Alias diagnostics.
 CARBON_DIAGNOSTIC_KIND(ExpectedAliasInitializer)
 
+// Where requirement diagnostics.
+CARBON_DIAGNOSTIC_KIND(ExpectedRequirementOperator)
+
 // ============================================================================
 // Semantics diagnostics
 // ============================================================================

+ 2 - 1
toolchain/lex/token_kind.def

@@ -212,7 +212,8 @@ CARBON_KEYWORD_TOKEN(Type,                "type")
 // Underscore is tokenized as a keyword because it's part of identifiers.
 CARBON_KEYWORD_TOKEN(Underscore,          "_")
 CARBON_KEYWORD_TOKEN(Virtual,             "virtual")
-CARBON_KEYWORD_TOKEN(Where,               "where")
+CARBON_TOKEN_WITH_VIRTUAL_NODE(
+  CARBON_KEYWORD_TOKEN(Where,             "where"))
 CARBON_KEYWORD_TOKEN(While,               "while")
 
 // clang-format on

+ 9 - 0
toolchain/parse/handle_expr.cpp

@@ -323,6 +323,15 @@ auto HandleExprLoop(Context& context) -> void {
         state.state = State::ExprLoopForShortCircuitOperatorAsOr;
         break;
 
+      // `where` also needs a virtual parse tree node, and parses its right
+      // argument in a mode where it can handle requirement operators like
+      // `impls` and `=`.
+      case Lex::TokenKind::Where:
+        context.AddNode(NodeKind::WhereOperand, state.token, state.has_error);
+        context.PushState(state, State::WhereFinish);
+        context.PushState(State::RequirementBegin);
+        return;
+
       default:
         state.state = State::ExprLoopForInfixOperator;
         break;

+ 83 - 0
toolchain/parse/handle_requirement.cpp

@@ -0,0 +1,83 @@
+// 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
+
+#include "toolchain/lex/token_kind.h"
+#include "toolchain/parse/context.h"
+#include "toolchain/parse/handle.h"
+
+namespace Carbon::Parse {
+
+auto HandleRequirementBegin(Context& context) -> void {
+  context.PopAndDiscardState();
+  context.PushState(State::RequirementOperator);
+  context.PushStateForExpr(PrecedenceGroup::ForRequirements());
+}
+
+auto HandleRequirementOperator(Context& context) -> void {
+  auto state = context.PopState();
+
+  switch (context.PositionKind()) {
+    case Lex::TokenKind::Impls: {
+      break;
+    }
+    case Lex::TokenKind::Equal: {
+      break;
+    }
+    case Lex::TokenKind::EqualEqual: {
+      break;
+    }
+    default: {
+      if (!state.has_error) {
+        CARBON_DIAGNOSTIC(
+            ExpectedRequirementOperator, Error,
+            "Requirement should use `impls`, `=`, or `==` operator.");
+        context.emitter().Emit(*context.position(),
+                               ExpectedRequirementOperator);
+      }
+      context.ReturnErrorOnState();
+      return;
+    }
+  }
+  state.token = context.Consume();
+  context.PushState(state, State::RequirementOperatorFinish);
+  context.PushStateForExpr(PrecedenceGroup::ForRequirements());
+}
+
+auto HandleRequirementOperatorFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  switch (auto token_kind = context.tokens().GetKind(state.token)) {
+    case Lex::TokenKind::Impls: {
+      context.AddNode(NodeKind::RequirementImpls, state.token, state.has_error);
+      break;
+    }
+    case Lex::TokenKind::Equal: {
+      context.AddNode(NodeKind::RequirementEqual, state.token, state.has_error);
+      break;
+    }
+    case Lex::TokenKind::EqualEqual: {
+      context.AddNode(NodeKind::RequirementEqualEqual, state.token,
+                      state.has_error);
+      break;
+    }
+    default:
+      // RequirementOperatorFinish state is only pushed in
+      // HandleRequirementOperator on one of the three requirement operator
+      // tokens.
+      CARBON_FATAL() << "Unexpected token kind for requirement operator: "
+                     << token_kind;
+      return;
+  }
+  // TODO: Handle `and` token.
+}
+
+auto HandleWhereFinish(Context& context) -> void {
+  auto state = context.PopState();
+  if (state.has_error) {
+    context.ReturnErrorOnState();
+  }
+  context.AddNode(NodeKind::WhereExpr, state.token, state.has_error);
+}
+
+}  // namespace Carbon::Parse

+ 1 - 0
toolchain/parse/node_ids.h

@@ -77,6 +77,7 @@ using AnyMemberAccessId =
 using AnyModifierId = NodeIdInCategory<NodeCategory::Modifier>;
 using AnyPatternId = NodeIdInCategory<NodeCategory::Pattern>;
 using AnyStatementId = NodeIdInCategory<NodeCategory::Statement>;
+using AnyRequirementId = NodeIdInCategory<NodeCategory::Requirement>;
 
 // NodeId with kind that matches one of the `T::Kind`s.
 template <typename... T>

+ 5 - 0
toolchain/parse/node_kind.def

@@ -276,6 +276,11 @@ CARBON_PARSE_NODE_KIND(ShortCircuitOperatorOr)
 
 CARBON_PARSE_NODE_KIND(SelfTypeName)
 CARBON_PARSE_NODE_KIND(DesignatorExpr)
+CARBON_PARSE_NODE_KIND(RequirementEqual)
+CARBON_PARSE_NODE_KIND(RequirementEqualEqual)
+CARBON_PARSE_NODE_KIND(RequirementImpls)
+CARBON_PARSE_NODE_KIND(WhereExpr)
+CARBON_PARSE_NODE_KIND(WhereOperand)
 
 CARBON_PARSE_NODE_KIND(IfExprIf)
 CARBON_PARSE_NODE_KIND(IfExprThen)

+ 2 - 1
toolchain/parse/node_kind.h

@@ -34,9 +34,10 @@ class NodeCategory : public Printable<NodeCategory> {
     Pattern = 1 << 6,
     Statement = 1 << 7,
     IntConst = 1 << 8,
+    Requirement = 1 << 9,
     None = 0,
 
-    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/IntConst)
+    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Requirement)
   };
 
   // Support implicit conversion so that the difference with the member enum is

+ 12 - 2
toolchain/parse/precedence.cpp

@@ -29,6 +29,8 @@ enum PrecedenceLevel : int8_t {
   // Type formation.
   TypePrefix,
   TypePostfix,
+  // `where` keyword.
+  Where,
   // Casts.
   As,
   // Logical.
@@ -60,9 +62,9 @@ struct OperatorPriorityTable {
     MarkHigherThan({Multiplicative}, {Additive});
     MarkHigherThan(
         {Additive, Modulo, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift},
-        {Relational});
+        {Relational, Where});
     MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr});
-    MarkHigherThan({As, LogicalAnd, LogicalOr}, {If});
+    MarkHigherThan({As, LogicalAnd, LogicalOr, Where}, {If});
     MarkHigherThan({If}, {Assignment});
     MarkHigherThan({Assignment, IncrementDecrement}, {Lowest});
 
@@ -194,6 +196,10 @@ auto PrecedenceGroup::ForImplAs() -> PrecedenceGroup {
   return PrecedenceGroup(As);
 }
 
+auto PrecedenceGroup::ForRequirements() -> PrecedenceGroup {
+  return PrecedenceGroup(Where);
+}
+
 auto PrecedenceGroup::ForLeading(Lex::TokenKind kind)
     -> std::optional<PrecedenceGroup> {
   switch (kind) {
@@ -289,6 +295,10 @@ auto PrecedenceGroup::ForTrailing(Lex::TokenKind kind, bool infix)
     case Lex::TokenKind::As:
       return Trailing{.level = As, .is_binary = true};
 
+    // Requirement operator.
+    case Lex::TokenKind::Where:
+      return Trailing{.level = Where, .is_binary = true};
+
     // Prefix-only operators.
     case Lex::TokenKind::Const:
     case Lex::TokenKind::MinusMinus:

+ 4 - 0
toolchain/parse/precedence.h

@@ -58,6 +58,10 @@ class PrecedenceGroup {
   // `impl` and `as`.
   static auto ForImplAs() -> PrecedenceGroup;
 
+  // Get the precedence level at which to parse expressions in requirements
+  // after `where` or `require`.
+  static auto ForRequirements() -> PrecedenceGroup;
+
   // Look up the operator information of the given prefix operator token, or
   // return std::nullopt if the given token is not a prefix operator.
   static auto ForLeading(Lex::TokenKind kind) -> std::optional<PrecedenceGroup>;

+ 41 - 1
toolchain/parse/state.def

@@ -17,7 +17,7 @@
 //
 // The comments before each state describe the portion of the grammar that the
 // state is implementing, by giving an example of each kind of token sequence
-// that this state handles. In this example, `...` indicates a sequence of
+// that this state handles. In these examples, `...` indicates a sequence of
 // tokens handled by some other state, and `???` indicates a sequence of invalid
 // tokens. A trailing `??? ;` indicates an attempt to skip to the end of the
 // declaration, which may or may not actually find a `;` token.
@@ -614,6 +614,46 @@ CARBON_PARSE_STATE(IfExprFinishElse)
 //   (state done)
 CARBON_PARSE_STATE(IfExprFinish)
 
+// Handles the beginning of a requirement expression after a `where` operator in
+// an expression.
+// TODO: Also a `require` declaration?
+//
+// expr where ...
+//           ^
+//   1. Expr
+//   2. RequirementOperator
+CARBON_PARSE_STATE(RequirementBegin)
+
+// Handles a requirement operator in a `where` expression.
+//
+// expr where expr impls ...
+//                 ^~~~
+// expr where expr = ...
+//                 ^
+// expr where expr == ...
+//                 ^~
+//   1. Expr
+//   2. RequirementOperatorFinish
+CARBON_PARSE_STATE(RequirementOperator)
+
+// Finishes a requirement operator in a `where` expression.
+//
+// expr where expr impls expr
+//                           ^
+// expr where expr = expr
+//                       ^
+// expr where expr == expr
+//                        ^
+//   (state done)
+CARBON_PARSE_STATE(RequirementOperatorFinish)
+
+// Finishes an `where` expression.
+//
+// expr where requirement
+//                       ^
+//   (state done)
+CARBON_PARSE_STATE(WhereFinish)
+
 // Handles the `;` for an expression statement, which is different from most
 // keyword statements.
 //

+ 271 - 0
toolchain/parse/testdata/operators/fail_precedence_where.carbon

@@ -0,0 +1,271 @@
+// 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/operators/fail_precedence_where.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/fail_precedence_where.carbon
+
+// --- fail_and_where.carbon
+
+fn AndWhere() {
+  // CHECK:STDERR: fail_and_where.carbon:[[@LINE+4]]:14: ERROR: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   true and T where .A = B;
+  // CHECK:STDERR:              ^~~~~
+  // CHECK:STDERR:
+  true and T where .A = B;
+}
+
+// --- fail_where_or.carbon
+
+fn WhereOr() {
+  // CHECK:STDERR: fail_where_or.carbon:[[@LINE+4]]:22: ERROR: Expected `;` after expression statement.
+  // CHECK:STDERR:   U where .C impls D or false;
+  // CHECK:STDERR:                      ^~
+  // CHECK:STDERR:
+  U where .C impls D or false;
+}
+
+// --- fail_equals_where.carbon
+
+fn EqualsWhere() {
+  // CHECK:STDERR: fail_equals_where.carbon:[[@LINE+4]]:10: ERROR: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   e == V where .G == f32;
+  // CHECK:STDERR:          ^~~~~
+  // CHECK:STDERR:
+  e == V where .G == f32;
+}
+
+// --- fail_where_neq.carbon
+
+fn WhereNEq() {
+  // CHECK:STDERR: fail_where_neq.carbon:[[@LINE+4]]:21: ERROR: Expected `;` after expression statement.
+  // CHECK:STDERR:   W where .H = bool != i;
+  // CHECK:STDERR:                     ^~
+  // CHECK:STDERR:
+  W where .H = bool != i;
+}
+
+// --- fail_less_requirement.carbon
+
+fn LessRequirement() {
+  // CHECK:STDERR: fail_less_requirement.carbon:[[@LINE+4]]:14: ERROR: Requirement should use `impls`, `=`, or `==` operator.
+  // CHECK:STDERR:   X where .J < .K;
+  // CHECK:STDERR:              ^
+  // CHECK:STDERR:
+  X where .J < .K;
+}
+
+// --- fail_as_where.carbon
+
+fn AsWhere() {
+  // CHECK:STDERR: fail_as_where.carbon:[[@LINE+4]]:10: ERROR: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   Y as Z where .L impls M;
+  // CHECK:STDERR:          ^~~~~
+  // CHECK:STDERR:
+  Y as Z where .L impls M;
+}
+
+// --- fail_where_as.carbon
+
+fn WhereAs() {
+  // CHECK:STDERR: fail_where_as.carbon:[[@LINE+4]]:21: ERROR: Expected `;` after expression statement.
+  // CHECK:STDERR:   S where .N == i32 as type;
+  // CHECK:STDERR:                     ^~
+  // CHECK:STDERR:
+  S where .N == i32 as type;
+}
+
+// --- fail_as_in_argument.carbon
+
+fn AsInArgument() {
+  // CHECK:STDERR: fail_as_in_argument.carbon:[[@LINE+4]]:14: ERROR: Requirement should use `impls`, `=`, or `==` operator.
+  // CHECK:STDERR:   R where .O as type impls P;
+  // CHECK:STDERR:              ^~
+  // CHECK:STDERR:
+  R where .O as type impls P;
+}
+
+// --- fail_greater_in_argument.carbon
+
+fn GreaterInArgument() {
+  // CHECK:STDERR: fail_greater_in_argument.carbon:[[@LINE+3]]:15: ERROR: Requirement should use `impls`, `=`, or `==` operator.
+  // CHECK:STDERR:   Q where .AA > .BB impls CC;
+  // CHECK:STDERR:               ^
+  Q where .AA > .BB impls CC;
+}
+
+// CHECK:STDOUT: - filename: fail_and_where.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'AndWhere'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:                 {kind: 'BoolLiteralTrue', text: 'true'},
+// CHECK:STDOUT:               {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'T'},
+// CHECK:STDOUT:             {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'A'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'B'},
+// CHECK:STDOUT:           {kind: 'RequirementEqual', text: '=', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 17},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_where_or.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'WhereOr'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'U'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'C'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'D'},
+// CHECK:STDOUT:           {kind: 'RequirementImpls', text: 'impls', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_equals_where.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'EqualsWhere'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'e'},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'V'},
+// CHECK:STDOUT:             {kind: 'InfixOperatorEqualEqual', text: '==', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'G'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'FloatTypeLiteral', text: 'f32'},
+// CHECK:STDOUT:           {kind: 'RequirementEqualEqual', text: '==', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 9},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_where_neq.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'WhereNEq'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'W'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'H'},
+// 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: 7},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_less_requirement.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'LessRequirement'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'X'},
+// 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: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_as_where.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'AsWhere'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'Y'},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'Z'},
+// CHECK:STDOUT:             {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'L'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'M'},
+// CHECK:STDOUT:           {kind: 'RequirementImpls', text: 'impls', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 9},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_where_as.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'WhereAs'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'S'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'N'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'RequirementEqualEqual', text: '==', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_as_in_argument.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'AsInArgument'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'R'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 'O'},
+// CHECK:STDOUT:           {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_greater_in_argument.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'GreaterInArgument'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'Q'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 'AA'},
+// CHECK:STDOUT:           {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 95 - 0
toolchain/parse/testdata/operators/precedence_where.carbon

@@ -0,0 +1,95 @@
+// 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/operators/precedence_where.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/precedence_where.carbon
+
+fn PostfixStar(T:! I* where .A* == .B*);
+
+fn Ampersand(U:! J & K where C & D impls E & F);
+
+fn BinaryPlus(V:! L + M where G & H impls N & O);
+
+fn UnaryMinus(W:! -P where -Q impls -R);
+
+// CHECK:STDOUT: - filename: precedence_where.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'PostfixStar'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'T'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'I'},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'WhereOperand', text: 'where', subtree_size: 3},
+// CHECK:STDOUT:                   {kind: 'IdentifierName', text: 'A'},
+// CHECK:STDOUT:                 {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:                   {kind: 'IdentifierName', text: 'B'},
+// CHECK:STDOUT:                 {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'RequirementEqualEqual', text: '==', subtree_size: 7},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 11},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 13},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 15},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 18},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Ampersand'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'U'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'J'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'K'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorAmp', text: '&', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'WhereOperand', text: 'where', subtree_size: 4},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'C'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'D'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorAmp', text: '&', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'E'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'F'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorAmp', text: '&', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'RequirementImpls', text: 'impls', subtree_size: 7},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 14},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 19},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'BinaryPlus'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'V'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'L'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'M'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'WhereOperand', text: 'where', subtree_size: 4},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'G'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'H'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorAmp', text: '&', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'N'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'O'},
+// CHECK:STDOUT:               {kind: 'InfixOperatorAmp', text: '&', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'RequirementImpls', text: 'impls', subtree_size: 7},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 14},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 19},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'UnaryMinus'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'W'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'P'},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'WhereOperand', text: 'where', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'Q'},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'R'},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'RequirementImpls', text: 'impls', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 11},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 13},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 86 - 0
toolchain/parse/testdata/where_expr/basic.carbon

@@ -0,0 +1,86 @@
+// 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/basic.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/where_expr/basic.carbon
+
+fn Foo(T: type where .Self == i32);
+
+interface I {
+  let T:! type where .Foo impls Bar;
+}
+
+let T:! type where .U = .V;
+
+fn F() {
+  let U:! type where .W == .X;
+}
+
+// CHECK:STDOUT: - filename: basic.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: 'SelfTypeName', text: 'Self'},
+// CHECK:STDOUT:               {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:             {kind: 'RequirementEqualEqual', 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: ')', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 14},
+// CHECK:STDOUT:         {kind: 'InterfaceIntroducer', text: 'interface'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'I'},
+// CHECK:STDOUT:       {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'LetIntroducer', text: 'let'},
+// 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: 'Foo'},
+// CHECK:STDOUT:               {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'Bar'},
+// CHECK:STDOUT:             {kind: 'RequirementImpls', text: 'impls', subtree_size: 4},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 9},
+// CHECK:STDOUT:       {kind: 'LetDecl', text: ';', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'InterfaceDefinition', text: '}', subtree_size: 15},
+// CHECK:STDOUT:       {kind: 'LetIntroducer', text: 'let'},
+// 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: 'IdentifierName', text: 'V'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'RequirementEqual', text: '=', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'LetDecl', text: ';', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'U'},
+// CHECK:STDOUT:               {kind: 'TypeTypeLiteral', text: 'type'},
+// CHECK:STDOUT:             {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:                 {kind: 'IdentifierName', text: 'W'},
+// CHECK:STDOUT:               {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:                 {kind: 'IdentifierName', text: 'X'},
+// CHECK:STDOUT:               {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'RequirementEqualEqual', text: '==', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'WhereExpr', text: 'where', subtree_size: 8},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 10},
+// CHECK:STDOUT:       {kind: 'LetDecl', text: ';', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 36 - 0
toolchain/parse/testdata/where_expr/fail_todo_where_and.carbon

@@ -0,0 +1,36 @@
+// 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:   ]

+ 57 - 0
toolchain/parse/testdata/where_expr/impl_where.carbon

@@ -0,0 +1,57 @@
+// 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/impl_where.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/where_expr/impl_where.carbon
+
+interface Interface {
+  let T:! type;
+}
+
+impl i32 as Interface where .T = () {
+}
+
+impl bool as Interface where .T = i32;
+
+// CHECK:STDOUT: - filename: impl_where.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'InterfaceIntroducer', text: 'interface'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'Interface'},
+// CHECK:STDOUT:       {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:           {kind: 'IdentifierName', text: 'T'},
+// CHECK:STDOUT:           {kind: 'TypeTypeLiteral', text: 'type'},
+// CHECK:STDOUT:         {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'LetDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'InterfaceDefinition', text: '}', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'ImplIntroducer', text: 'impl'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'TypeImplAs', text: 'as', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'Interface'},
+// CHECK:STDOUT:           {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierName', text: 'T'},
+// CHECK:STDOUT:             {kind: 'DesignatorExpr', text: '.', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:             {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'RequirementEqual', text: '=', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'WhereExpr', text: 'where', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'ImplDefinitionStart', text: '{', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'ImplDefinition', text: '}', subtree_size: 13},
+// CHECK:STDOUT:       {kind: 'ImplIntroducer', text: 'impl'},
+// CHECK:STDOUT:         {kind: 'BoolTypeLiteral', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'TypeImplAs', text: 'as', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'Interface'},
+// CHECK:STDOUT:         {kind: 'WhereOperand', text: 'where', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 'T'},
+// 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: 'ImplDecl', text: ';', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 46 - 0
toolchain/parse/typed_nodes.h

@@ -1016,6 +1016,8 @@ struct IfExprElse {
   AnyExprId else_result;
 };
 
+// A `where` expression (TODO: `require` and `observe` declarations)
+
 // The `Self` in a context where it is treated as a name rather than an
 // expression, such as `.Self`.
 using SelfTypeName =
@@ -1032,6 +1034,50 @@ struct DesignatorExpr {
   NodeIdOneOf<IdentifierName, SelfTypeName> name;
 };
 
+struct RequirementEqual {
+  static constexpr auto Kind = NodeKind::RequirementEqual.Define(
+      {.category = NodeCategory::Requirement, .child_count = 2});
+  // TODO: Enforce that the lhs is always a DesignatorExpr.
+  AnyExprId lhs;
+  Lex::EqualTokenIndex token;
+  AnyExprId rhs;
+};
+
+struct RequirementEqualEqual {
+  static constexpr auto Kind = NodeKind::RequirementEqualEqual.Define(
+      {.category = NodeCategory::Requirement, .child_count = 2});
+  AnyExprId lhs;
+  Lex::EqualEqualTokenIndex token;
+  AnyExprId rhs;
+};
+
+struct RequirementImpls {
+  static constexpr auto Kind = NodeKind::RequirementImpls.Define(
+      {.category = NodeCategory::Requirement, .child_count = 2});
+  AnyExprId lhs;
+  Lex::ImplsTokenIndex token;
+  AnyExprId rhs;
+};
+
+struct WhereOperand {
+  static constexpr auto Kind =
+      NodeKind::WhereOperand.Define({.child_count = 1});
+  AnyExprId type;
+  // This is a virtual token. The `where` token is owned by the
+  // WhereExpr node.
+  Lex::WhereTokenIndex token;
+};
+
+struct WhereExpr {
+  static constexpr auto Kind =
+      NodeKind::WhereExpr.Define({.category = NodeCategory::Expr,
+                                  .bracketed_by = WhereOperand::Kind,
+                                  .child_count = 2});
+  WhereOperandId introducer;
+  Lex::WhereTokenIndex token;
+  AnyRequirementId requirements;
+};
+
 // Choice nodes
 // ------------