From bdbd1079a6d1873fdc7efe927a2bd9a1ddaed70b Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:41:56 -0700 Subject: [PATCH] `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 Co-authored-by: Jon Ross-Perkins --- toolchain/check/context.cpp | 10 ++- toolchain/check/eval.cpp | 9 +++ toolchain/check/handle_where.cpp | 74 +++++++++++++------ toolchain/check/node_stack.h | 3 +- .../impl/fail_todo_impl_assoc_const.carbon | 3 + .../testdata/where_expr/constraints.carbon | 43 ++++++++--- .../where_expr/no_prelude/designator.carbon | 18 ++++- .../where_expr/no_prelude/non_generic.carbon | 5 +- toolchain/lower/file_context.cpp | 2 +- toolchain/sem_ir/file.cpp | 20 +++++ toolchain/sem_ir/formatter.cpp | 5 ++ toolchain/sem_ir/inst_kind.def | 4 + toolchain/sem_ir/typed_insts.h | 48 ++++++++++++ 13 files changed, 198 insertions(+), 46 deletions(-) diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 3260facf183e..434278d564aa 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -1073,10 +1073,12 @@ class TypeCompleter { } template - requires(InstT::Kind.template IsAnyOf< - SemIR::AssociatedEntityType, SemIR::FunctionType, - SemIR::GenericClassType, SemIR::GenericInterfaceType, - SemIR::InterfaceType, SemIR::UnboundElementType>()) + requires( + InstT::Kind + .template IsAnyOf()) auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const -> SemIR::ValueRepr { // These types have no runtime operations, so we use an empty value diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 8d96f1a8510e..0816f3d169da 100644 --- a/toolchain/check/eval.cpp +++ b/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: diff --git a/toolchain/check/handle_where.cpp b/toolchain/check/handle_where.cpp index 31a3f790ca9c..3cf5fa8ee390 100644 --- a/toolchain/check/handle_where.cpp +++ b/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( + 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(); - return true; -} - -auto HandleParseNode(Context& context, Parse::RequirementImplsId /*node_id*/) +auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId 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: 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( + node_id, {.lhs_id = lhs, .rhs_id = rhs})); + return true; +} + +auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id) + -> bool { + 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( + 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(); + SemIR::InstBlockId requirements_id = context.args_type_info_stack().Pop(); + context.AddInstAndPush( + node_id, {.type_id = SemIR::TypeId::TypeType, + .period_self_id = period_self_id, + .requirements_id = requirements_id}); return true; } diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index eb0f389304c1..986bcd426a28 100644 --- a/toolchain/check/node_stack.h +++ b/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(); 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(); - case Parse::NodeKind::WhereOperand: - return Id::KindFor(); case Parse::NodeKind::ArrayExprSemi: case Parse::NodeKind::BuiltinName: case Parse::NodeKind::ClassIntroducer: diff --git a/toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon b/toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon index a74808b02209..0c0a13fb71d3 100644 --- a/toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon +++ b/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: diff --git a/toolchain/check/testdata/where_expr/constraints.carbon b/toolchain/check/testdata/where_expr/constraints.carbon index 1f7a78fbf524..6c332297714e 100644 --- a/toolchain/check/testdata/where_expr/constraints.carbon +++ b/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 // 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 // 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 // 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 // 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 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 // 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 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 // CHECK:STDOUT: %Y.loc26: %.3 = bind_symbolic_name Y 0, %Y.param [symbolic = %Y.1 (constants.%Y)] // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/where_expr/no_prelude/designator.carbon b/toolchain/check/testdata/where_expr/no_prelude/designator.carbon index 01771c6cc0ea..f2768950762b 100644 --- a/toolchain/check/testdata/where_expr/no_prelude/designator.carbon +++ b/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 // 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 // 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 // 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: = name_ref Mismatch, [template = ] -// 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 // CHECK:STDOUT: %W.loc12: %.1 = bind_symbolic_name W 0, %W.param [symbolic = %W.1 (constants.%W)] // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/where_expr/no_prelude/non_generic.carbon b/toolchain/check/testdata/where_expr/no_prelude/non_generic.carbon index 60bc85f5c617..4b97691692f7 100644 --- a/toolchain/check/testdata/where_expr/no_prelude/non_generic.carbon +++ b/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: } diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 3f4e0e775289..c95820c3133a 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -499,7 +499,7 @@ template 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. diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 3f8bbbe52e39..e6a8c73b4cd7 100644 --- a/toolchain/sem_ir/file.cpp +++ b/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 << ""; + } + 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): { diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index e84daf6eb8d0..a0d9da245481 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/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 {} diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 76f12af2fcf6..e0aa75be79b3 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/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 diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index d4f2221e4411..8feecc3b9c0f 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/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( + {.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( + {.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( + {.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( + {.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 {