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

`where` check stage, step 2: SemIR (#4349)

The check stage now produces SemIR instructions to represent a `where`
clause. It still does not check types.

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
josh11b 1 год назад
Родитель
Сommit
bdbd1079a6

+ 6 - 4
toolchain/check/context.cpp

@@ -1073,10 +1073,12 @@ class TypeCompleter {
   }
 
   template <typename InstT>
-    requires(InstT::Kind.template IsAnyOf<
-             SemIR::AssociatedEntityType, SemIR::FunctionType,
-             SemIR::GenericClassType, SemIR::GenericInterfaceType,
-             SemIR::InterfaceType, SemIR::UnboundElementType>())
+    requires(
+        InstT::Kind
+            .template IsAnyOf<SemIR::AssociatedEntityType, SemIR::FunctionType,
+                              SemIR::GenericClassType,
+                              SemIR::GenericInterfaceType, SemIR::InterfaceType,
+                              SemIR::UnboundElementType, SemIR::WhereExpr>())
   auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const
       -> SemIR::ValueRepr {
     // These types have no runtime operations, so we use an empty value

+ 9 - 0
toolchain/check/eval.cpp

@@ -1403,6 +1403,12 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
       // here. For now, we model a facet value as just a type.
       return eval_context.GetConstantValue(typed_inst.facet_id);
     }
+    case CARBON_KIND(SemIR::WhereExpr typed_inst): {
+      // TODO: This currently ignores the requirements and just produces the
+      // left-hand type argument to the `where`.
+      return eval_context.GetConstantValue(
+          eval_context.insts().Get(typed_inst.period_self_id).type_id());
+    }
 
     // `not true` -> `false`, `not false` -> `true`.
     // All other uses of unary `not` are non-constant.
@@ -1448,6 +1454,9 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
     case SemIR::ImplDecl::Kind:
     case SemIR::ImportDecl::Kind:
     case SemIR::Param::Kind:
+    case SemIR::RequirementEquivalent::Kind:
+    case SemIR::RequirementImpls::Kind:
+    case SemIR::RequirementRewrite::Kind:
     case SemIR::ReturnExpr::Kind:
     case SemIR::Return::Kind:
     case SemIR::StructLiteral::Kind:

+ 48 - 20
toolchain/check/handle_where.cpp

@@ -9,16 +9,14 @@
 
 namespace Carbon::Check {
 
-auto HandleParseNode(Context& context, Parse::WhereOperandId /*node_id*/)
-    -> bool {
+auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
   // The expression at the top of the stack represents a constraint type that
   // is being modified by the `where` operator. It would be `MyInterface` in
   // `MyInterface where .Member = i32`.
   auto [self_node, self_id] = context.node_stack().PopExprWithNodeId();
   auto self_type_id = ExprAsType(context, self_node, self_id);
-  // TODO: Do this instead once `WhereExpr` is ready to consume this:
-  // context.node_stack().Push(node_id, self_type_id);
-  context.node_stack().Push(self_node, self_id);
+  // TODO: Validate that `self_type_id` represents a facet type. Only facet
+  // types may have `where` restrictions.
 
   // Introduce a name scope so that we can remove the `.Self` entry we are
   // adding to name lookup at the end of the `where` expression.
@@ -43,30 +41,54 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId /*node_id*/)
       context.scope_stack().LookupOrAddName(SemIR::NameId::PeriodSelf, inst_id);
   // Shouldn't have any names in newly created scope.
   CARBON_CHECK(!existing.is_valid());
+
+  // Save the `.Self` symbolic binding on the node stack. It will become the
+  // first argument to the `WhereExpr` instruction.
+  context.node_stack().Push(node_id, inst_id);
+
+  // Going to put each requirement on `args_type_info_stack`, so we can have an
+  // inst block with the varying number of requirements but keeping other
+  // instructions on the current inst block from the `inst_block_stack()`.
+  context.args_type_info_stack().Push();
   return true;
 }
 
-auto HandleParseNode(Context& context, Parse::RequirementEqualId /*node_id*/)
+auto HandleParseNode(Context& context, Parse::RequirementEqualId node_id)
     -> bool {
-  // TODO: Implement
-  context.node_stack().PopExpr();
-  context.node_stack().PopExpr();
+  auto rhs = context.node_stack().PopExpr();
+  auto lhs = context.node_stack().PopExpr();
+  // TODO: convert rhs to type of lhs
+
+  // Build up the list of arguments for the `WhereExpr` inst.
+  context.args_type_info_stack().AddInstId(
+      context.AddInstInNoBlock<SemIR::RequirementRewrite>(
+          node_id, {.lhs_id = lhs, .rhs_id = rhs}));
   return true;
 }
 
-auto HandleParseNode(Context& context,
-                     Parse::RequirementEqualEqualId /*node_id*/) -> bool {
-  // TODO: Implement
-  context.node_stack().PopExpr();
-  context.node_stack().PopExpr();
+auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)
+    -> bool {
+  auto rhs = context.node_stack().PopExpr();
+  auto lhs = context.node_stack().PopExpr();
+  // TODO: type check lhs and rhs are compatible
+
+  // Build up the list of arguments for the `WhereExpr` inst.
+  context.args_type_info_stack().AddInstId(
+      context.AddInstInNoBlock<SemIR::RequirementEquivalent>(
+          node_id, {.lhs_id = lhs, .rhs_id = rhs}));
   return true;
 }
 
-auto HandleParseNode(Context& context, Parse::RequirementImplsId /*node_id*/)
+auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
     -> bool {
-  // TODO: Implement
-  context.node_stack().PopExpr();
-  context.node_stack().PopExpr();
+  auto rhs = context.node_stack().PopExpr();
+  auto lhs = context.node_stack().PopExpr();
+  // TODO: check lhs is a facet and rhs is a facet type
+
+  // Build up the list of arguments for the `WhereExpr` inst.
+  context.args_type_info_stack().AddInstId(
+      context.AddInstInNoBlock<SemIR::RequirementImpls>(
+          node_id, {.lhs_id = lhs, .rhs_id = rhs}));
   return true;
 }
 
@@ -76,14 +98,20 @@ auto HandleParseNode(Context& /*context*/, Parse::RequirementAndId /*node_id*/)
   return true;
 }
 
-auto HandleParseNode(Context& context, Parse::WhereExprId /*node_id*/) -> bool {
+auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
   // Discard the generic region containing `.Self` and the constraints.
   // TODO: Decide if we want to build a `Generic` object for this.
   DiscardGenericDecl(context);
   // Remove `PeriodSelf` from name lookup, undoing the `Push` done for the
   // `WhereOperand`.
   context.scope_stack().Pop();
-  // TODO: Output instruction for newly formed restricted constraint type.
+  SemIR::InstId period_self_id =
+      context.node_stack().Pop<Parse::NodeKind::WhereOperand>();
+  SemIR::InstBlockId requirements_id = context.args_type_info_stack().Pop();
+  context.AddInstAndPush<SemIR::WhereExpr>(
+      node_id, {.type_id = SemIR::TypeId::TypeType,
+                .period_self_id = period_self_id,
+                .requirements_id = requirements_id});
   return true;
 }
 

+ 1 - 2
toolchain/check/node_stack.h

@@ -425,6 +425,7 @@ class NodeStack {
         case Parse::NodeKind::ShortCircuitOperandOr:
         case Parse::NodeKind::StructField:
         case Parse::NodeKind::StructTypeField:
+        case Parse::NodeKind::WhereOperand:
           return Id::KindFor<SemIR::InstId>();
         case Parse::NodeKind::IfCondition:
         case Parse::NodeKind::IfExprIf:
@@ -449,8 +450,6 @@ class NodeStack {
         case Parse::NodeKind::DefaultLibrary:
         case Parse::NodeKind::LibraryName:
           return Id::KindFor<SemIR::LibraryNameId>();
-        case Parse::NodeKind::WhereOperand:
-          return Id::KindFor<SemIR::TypeId>();
         case Parse::NodeKind::ArrayExprSemi:
         case Parse::NodeKind::BuiltinName:
         case Parse::NodeKind::ClassIntroducer:

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

@@ -59,6 +59,9 @@ impl bool as I where .T = bool {}
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %T.ref: %.2 = name_ref T, @I.%.loc11 [template = constants.%.3]
 // CHECK:STDOUT:     %bool.make_type.loc16_27: init type = call constants.%Bool() [template = bool]
+// CHECK:STDOUT:     %.loc16_16: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_rewrite %T.ref, %bool.make_type.loc16_27
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 31 - 12
toolchain/check/testdata/where_expr/constraints.carbon

@@ -141,7 +141,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %.Self: %.2 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %.Self.ref: %.2 = name_ref .Self, %.Self [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %Member.ref: %.3 = name_ref Member, @I.%.loc7 [template = constants.%.4]
-// CHECK:STDOUT:     %.loc11: %.8 = struct_literal ()
+// CHECK:STDOUT:     %.loc11_33: %.8 = struct_literal ()
+// CHECK:STDOUT:     %.loc11_16: type = where_expr %.Self [template = constants.%.2] {
+// CHECK:STDOUT:       requirement_rewrite %Member.ref, %.loc11_33
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %T.param: %.2 = param T, runtime_param<invalid>
 // CHECK:STDOUT:     %T.loc11: %.2 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T)]
 // CHECK:STDOUT:   }
@@ -151,7 +154,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.2]
 // CHECK:STDOUT:     %.Self: %.2 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %.Self.ref: %.2 = name_ref .Self, %.Self [symbolic = constants.%.Self.1]
-// CHECK:STDOUT:     %.loc13: %.5 = tuple_literal ()
+// CHECK:STDOUT:     %.loc13_37: %.5 = tuple_literal ()
+// CHECK:STDOUT:     %.loc13_21: type = where_expr %.Self [template = constants.%.2] {
+// CHECK:STDOUT:       requirement_equivalent %.Self.ref, %.loc13_37
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %U.param: %.2 = param U, runtime_param<invalid>
 // CHECK:STDOUT:     %U.loc13: %.2 = bind_symbolic_name U 0, %U.param [symbolic = %U.1 (constants.%U)]
 // CHECK:STDOUT:   }
@@ -162,6 +168,9 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.2]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self.2]
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.2]
+// CHECK:STDOUT:     %.loc15: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_impls %.Self.ref, %I.ref
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %V.param: %.1 = param V, runtime_param<invalid>
 // CHECK:STDOUT:     %V.loc15: %.1 = bind_symbolic_name V 0, %V.param [symbolic = %V.1 (constants.%V)]
 // CHECK:STDOUT:   }
@@ -177,6 +186,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %Member.ref: %.3 = name_ref Member, @I.%.loc7 [template = constants.%.4]
 // CHECK:STDOUT:     %.Self.ref.loc17_50: %.2 = name_ref .Self, %.Self [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %Second.ref.loc17_50: %.6 = name_ref Second, @I.%.loc8 [template = constants.%.7]
+// CHECK:STDOUT:     %.loc17: type = where_expr %.Self [template = constants.%.2] {
+// CHECK:STDOUT:       requirement_impls %Second.ref.loc17_20, %I.ref.loc17_34
+// CHECK:STDOUT:       requirement_rewrite %Member.ref, %Second.ref.loc17_50
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %W.param: %.2 = param W, runtime_param<invalid>
 // CHECK:STDOUT:     %W.loc17: %.2 = bind_symbolic_name W 0, %W.param [symbolic = %W.1 (constants.%W)]
 // CHECK:STDOUT:   }
@@ -263,10 +276,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1 = import_ref Main//state_constraints, inst+3, unloaded
 // CHECK:STDOUT:   %import_ref.2: type = import_ref Main//state_constraints, inst+7, loaded [template = constants.%.1]
-// CHECK:STDOUT:   %import_ref.3 = import_ref Main//state_constraints, inst+32, unloaded
-// CHECK:STDOUT:   %import_ref.4 = import_ref Main//state_constraints, inst+44, unloaded
-// CHECK:STDOUT:   %import_ref.5 = import_ref Main//state_constraints, inst+57, unloaded
-// CHECK:STDOUT:   %import_ref.6 = import_ref Main//state_constraints, inst+74, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//state_constraints, inst+34, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref Main//state_constraints, inst+48, unloaded
+// CHECK:STDOUT:   %import_ref.5 = import_ref Main//state_constraints, inst+63, unloaded
+// CHECK:STDOUT:   %import_ref.6 = import_ref Main//state_constraints, inst+83, unloaded
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     import Core//prelude
 // CHECK:STDOUT:     import Core//prelude/operators
@@ -305,7 +318,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %Member.ref: %.3 = name_ref Member, imports.%import_ref.8 [template = constants.%.4]
-// CHECK:STDOUT:     %.loc8: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:     %.loc8_39: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:     %.loc8_23: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_rewrite %Member.ref, %.loc8_39
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %X.param: %.1 = param X, runtime_param<invalid>
 // CHECK:STDOUT:     %X.loc8: %.1 = bind_symbolic_name X 0, %X.param [symbolic = %X.1 (constants.%X)]
 // CHECK:STDOUT:   }
@@ -371,10 +387,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1: type = import_ref Main//state_constraints, inst+3, loaded [template = constants.%.3]
 // CHECK:STDOUT:   %import_ref.2 = import_ref Main//state_constraints, inst+7, unloaded
-// CHECK:STDOUT:   %import_ref.3 = import_ref Main//state_constraints, inst+32, unloaded
-// CHECK:STDOUT:   %import_ref.4 = import_ref Main//state_constraints, inst+44, unloaded
-// CHECK:STDOUT:   %import_ref.5: %Impls.type = import_ref Main//state_constraints, inst+57, loaded [template = constants.%Impls]
-// CHECK:STDOUT:   %import_ref.6 = import_ref Main//state_constraints, inst+74, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//state_constraints, inst+34, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref Main//state_constraints, inst+48, unloaded
+// CHECK:STDOUT:   %import_ref.5: %Impls.type = import_ref Main//state_constraints, inst+63, loaded [template = constants.%Impls]
+// CHECK:STDOUT:   %import_ref.6 = import_ref Main//state_constraints, inst+83, unloaded
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     .ImplicitAs = %import_ref.8
 // CHECK:STDOUT:     import Core//prelude
@@ -423,7 +439,10 @@ fn NotEmptyStruct() {
 // CHECK:STDOUT:     %J.ref: type = name_ref J, imports.%import_ref.1 [template = constants.%.3]
 // CHECK:STDOUT:     %.Self: %.3 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %.Self.ref: %.3 = name_ref .Self, %.Self [symbolic = constants.%.Self]
-// CHECK:STDOUT:     %.loc26: %.1 = struct_literal ()
+// CHECK:STDOUT:     %.loc26_38: %.1 = struct_literal ()
+// CHECK:STDOUT:     %.loc26_22: type = where_expr %.Self [template = constants.%.3] {
+// CHECK:STDOUT:       requirement_equivalent %.Self.ref, %.loc26_38
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %Y.param: %.3 = param Y, runtime_param<invalid>
 // CHECK:STDOUT:     %Y.loc26: %.3 = bind_symbolic_name Y 0, %Y.param [symbolic = %Y.1 (constants.%Y)]
 // CHECK:STDOUT:   }

+ 15 - 3
toolchain/check/testdata/where_expr/no_prelude/designator.carbon

@@ -122,7 +122,10 @@ class D {
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self.1]
-// CHECK:STDOUT:     %.loc8: %.4 = tuple_literal ()
+// CHECK:STDOUT:     %.loc8_37: %.4 = tuple_literal ()
+// CHECK:STDOUT:     %.loc8_21: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_equivalent %.Self.ref, %.loc8_37
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %T.param: %.1 = param T, runtime_param<invalid>
 // CHECK:STDOUT:     %T.loc8: %.1 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T)]
 // CHECK:STDOUT:   }
@@ -133,7 +136,10 @@ class D {
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self.1]
 // CHECK:STDOUT:     %Member.ref: %.2 = name_ref Member, @I.%.loc5 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc10: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc10_40: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc10_23: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_rewrite %Member.ref, %.loc10_40
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %U.param: %.1 = param U, runtime_param<invalid>
 // CHECK:STDOUT:     %U.loc10: %.1 = bind_symbolic_name U 0, %U.param [symbolic = %U.1 (constants.%U)]
 // CHECK:STDOUT:   }
@@ -143,6 +149,9 @@ class D {
 // CHECK:STDOUT:     %.Self: type = bind_symbolic_name .Self 0 [symbolic = constants.%.Self.2]
 // CHECK:STDOUT:     %.Self.ref: type = name_ref .Self, %.Self [symbolic = constants.%.Self.2]
 // CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
+// CHECK:STDOUT:     %.loc12: type = where_expr %.Self [template = type] {
+// CHECK:STDOUT:       requirement_impls %.Self.ref, %I.ref
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %V.param: type = param V, runtime_param<invalid>
 // CHECK:STDOUT:     %V.loc12: type = bind_symbolic_name V 0, %V.param [symbolic = %V.1 (constants.%V)]
 // CHECK:STDOUT:   }
@@ -217,7 +226,10 @@ class D {
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %Mismatch.ref: <error> = name_ref Mismatch, <error> [template = <error>]
-// CHECK:STDOUT:     %.loc12: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc12_44: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc12_25: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_rewrite %Mismatch.ref, %.loc12_44
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %W.param: %.1 = param W, runtime_param<invalid>
 // CHECK:STDOUT:     %W.loc12: %.1 = bind_symbolic_name W 0, %W.param [symbolic = %W.1 (constants.%W)]
 // CHECK:STDOUT:   }

+ 4 - 1
toolchain/check/testdata/where_expr/no_prelude/non_generic.carbon

@@ -40,7 +40,10 @@ fn NotGenericF(U: I where .T = {}) {}
 // CHECK:STDOUT:     %.Self: %.1 = bind_symbolic_name .Self 0 [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %.Self.ref: %.1 = name_ref .Self, %.Self [symbolic = constants.%.Self]
 // CHECK:STDOUT:     %T.ref: %.2 = name_ref T, @I.%.loc11 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc14: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc14_33: %.5 = struct_literal ()
+// CHECK:STDOUT:     %.loc14_21: type = where_expr %.Self [template = constants.%.1] {
+// CHECK:STDOUT:       requirement_rewrite %T.ref, %.loc14_33
+// CHECK:STDOUT:     }
 // CHECK:STDOUT:     %U.param: %.1 = param U, runtime_param0
 // CHECK:STDOUT:     %U: %.1 = bind_name U, %U.param
 // CHECK:STDOUT:   }

+ 1 - 1
toolchain/lower/file_context.cpp

@@ -499,7 +499,7 @@ template <typename InstT>
   requires(InstT::Kind.template IsAnyOf<
            SemIR::AssociatedEntityType, SemIR::FunctionType,
            SemIR::GenericClassType, SemIR::GenericInterfaceType,
-           SemIR::InterfaceType, SemIR::UnboundElementType>())
+           SemIR::InterfaceType, SemIR::UnboundElementType, SemIR::WhereExpr>())
 static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
     -> llvm::Type* {
   // Return an empty struct as a placeholder.

+ 20 - 0
toolchain/sem_ir/file.cpp

@@ -411,6 +411,19 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         }
         break;
       }
+      case CARBON_KIND(WhereExpr inst): {
+        if (step.index == 0) {
+          out << "<where restriction on ";
+          steps.push_back(step.Next());
+          TypeId type_id = sem_ir.insts().Get(inst.period_self_id).type_id();
+          push_inst_id(sem_ir.types().GetInstId(type_id));
+          // TODO: also output restrictions from the inst block
+          // inst.requirements_id
+        } else {
+          out << ">";
+        }
+        break;
+      }
       case AdaptDecl::Kind:
       case AddrOf::Kind:
       case AddrPattern::Kind:
@@ -453,6 +466,9 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       case IntLiteral::Kind:
       case Namespace::Kind:
       case Param::Kind:
+      case RequirementEquivalent::Kind:
+      case RequirementImpls::Kind:
+      case RequirementRewrite::Kind:
       case Return::Kind:
       case ReturnExpr::Kind:
       case SpliceBlock::Kind:
@@ -517,6 +533,9 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case FunctionDecl::Kind:
       case ImplDecl::Kind:
       case Namespace::Kind:
+      case RequirementEquivalent::Kind:
+      case RequirementImpls::Kind:
+      case RequirementRewrite::Kind:
       case Return::Kind:
       case ReturnExpr::Kind:
       case StructTypeField::Kind:
@@ -598,6 +617,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case UnaryOperatorNot::Kind:
       case UnboundElementType::Kind:
       case ValueOfInitializer::Kind:
+      case WhereExpr::Kind:
         return value_category;
 
       case CARBON_KIND(BuiltinInst inst): {

+ 5 - 0
toolchain/sem_ir/formatter.cpp

@@ -857,6 +857,11 @@ class FormatterImpl {
     FormatTrailingBlock(inst.block_id);
   }
 
+  auto FormatInstRHS(WhereExpr inst) -> void {
+    FormatArgs(inst.period_self_id);
+    FormatTrailingBlock(inst.requirements_id);
+  }
+
   // StructTypeFields are formatted as part of their StructType.
   auto FormatInst(InstId /*inst_id*/, StructTypeField /*inst*/) -> void {}
 

+ 4 - 0
toolchain/sem_ir/inst_kind.def

@@ -75,6 +75,9 @@ CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)
 CARBON_SEM_IR_INST_KIND(Param)
 CARBON_SEM_IR_INST_KIND(PointerType)
+CARBON_SEM_IR_INST_KIND(RequirementRewrite)
+CARBON_SEM_IR_INST_KIND(RequirementEquivalent)
+CARBON_SEM_IR_INST_KIND(RequirementImpls)
 CARBON_SEM_IR_INST_KIND(ReturnExpr)
 CARBON_SEM_IR_INST_KIND(Return)
 CARBON_SEM_IR_INST_KIND(SpliceBlock)
@@ -98,5 +101,6 @@ CARBON_SEM_IR_INST_KIND(UnboundElementType)
 CARBON_SEM_IR_INST_KIND(ValueAsRef)
 CARBON_SEM_IR_INST_KIND(ValueOfInitializer)
 CARBON_SEM_IR_INST_KIND(VarStorage)
+CARBON_SEM_IR_INST_KIND(WhereExpr)
 
 #undef CARBON_SEM_IR_INST_KIND

+ 48 - 0
toolchain/sem_ir/typed_insts.h

@@ -745,6 +745,7 @@ struct InterfaceWitness {
        .constant_kind = InstConstantKind::Conditional,
        .is_lowered = false});
 
+  // Always the builtin witness type.
   TypeId type_id;
   InstBlockId elements_id;
 };
@@ -857,6 +858,39 @@ struct ReturnExpr {
   InstId dest_id;
 };
 
+// An `expr == expr` clause in a `where` expression or `require` declaration.
+struct RequirementEquivalent {
+  static constexpr auto Kind =
+      InstKind::RequirementEquivalent.Define<Parse::RequirementEqualEqualId>(
+          {.ir_name = "requirement_equivalent", .is_lowered = false});
+
+  // No type since not an expression
+  InstId lhs_id;
+  InstId rhs_id;
+};
+
+// An `expr impls expr` clause in a `where` expression or `require` declaration.
+struct RequirementImpls {
+  static constexpr auto Kind =
+      InstKind::RequirementImpls.Define<Parse::RequirementImplsId>(
+          {.ir_name = "requirement_impls", .is_lowered = false});
+
+  // No type since not an expression
+  InstId lhs_id;
+  InstId rhs_id;
+};
+
+// A `.M = expr` clause in a `where` expression or `require` declaration.
+struct RequirementRewrite {
+  static constexpr auto Kind =
+      InstKind::RequirementRewrite.Define<Parse::RequirementEqualId>(
+          {.ir_name = "requirement_rewrite", .is_lowered = false});
+
+  // No type since not an expression
+  InstId lhs_id;
+  InstId rhs_id;
+};
+
 // Given an instruction with a constant value that depends on a generic
 // parameter, selects a version of that instruction with the constant value
 // corresponding to a particular specific.
@@ -1102,6 +1136,20 @@ struct VarStorage {
   NameId name_id;
 };
 
+// An `expr where requirements` expression.
+struct WhereExpr {
+  static constexpr auto Kind = InstKind::WhereExpr.Define<Parse::WhereExprId>(
+      {.ir_name = "where_expr",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::Conditional});
+
+  TypeId type_id;
+  // This is the `.Self` symbolic binding. Its type matches the left type
+  // argument of the `where`.
+  InstId period_self_id;
+  InstBlockId requirements_id;
+};
+
 // These concepts are an implementation detail of the library, not public API.
 namespace Internal {